From 770bbd806f0bb6c65325c0ffe541ba80d8040fb4 Mon Sep 17 00:00:00 2001 From: sid597 Date: Sun, 15 Feb 2026 14:22:42 +0530 Subject: [PATCH 1/5] ENG-1281: Port discourse node block panel --- .../settings/DiscourseNodeSuggestiveRules.tsx | 9 +- .../src/components/settings/NodeConfig.tsx | 9 +- .../components/BlockPropSettingPanels.tsx | 2 +- .../components/EphemeralBlocksPanel.tsx | 107 ++++++++++++++++++ .../settings/utils/zodSchema.example.ts | 4 - .../components/settings/utils/zodSchema.ts | 1 - 6 files changed, 116 insertions(+), 16 deletions(-) create mode 100644 apps/roam/src/components/settings/components/EphemeralBlocksPanel.tsx diff --git a/apps/roam/src/components/settings/DiscourseNodeSuggestiveRules.tsx b/apps/roam/src/components/settings/DiscourseNodeSuggestiveRules.tsx index 70088313f..f18fd2a3d 100644 --- a/apps/roam/src/components/settings/DiscourseNodeSuggestiveRules.tsx +++ b/apps/roam/src/components/settings/DiscourseNodeSuggestiveRules.tsx @@ -1,6 +1,6 @@ import React, { useState, useMemo, useEffect, useRef } from "react"; import { Button, Intent } from "@blueprintjs/core"; -import BlocksPanel from "roamjs-components/components/ConfigPanels/BlocksPanel"; +import DualWriteBlocksPanel from "./components/EphemeralBlocksPanel"; import getSubTree from "roamjs-components/util/getSubTree"; import { DiscourseNode } from "~/utils/getDiscourseNodes"; import extractRef from "roamjs-components/util/extractRef"; @@ -81,13 +81,12 @@ const DiscourseNodeSuggestiveRules = ({ return (
- -
} diff --git a/apps/roam/src/components/settings/components/BlockPropSettingPanels.tsx b/apps/roam/src/components/settings/components/BlockPropSettingPanels.tsx index 1e1b0d6f2..0002c8028 100644 --- a/apps/roam/src/components/settings/components/BlockPropSettingPanels.tsx +++ b/apps/roam/src/components/settings/components/BlockPropSettingPanels.tsx @@ -582,7 +582,7 @@ const createDiscourseNodeSetter = (keys: string[], value: json): void => setDiscourseNodeSetting(nodeType, keys, value); -type DiscourseNodeBaseProps = { +export type DiscourseNodeBaseProps = { nodeType: string; title: string; description: string; diff --git a/apps/roam/src/components/settings/components/EphemeralBlocksPanel.tsx b/apps/roam/src/components/settings/components/EphemeralBlocksPanel.tsx new file mode 100644 index 000000000..0d8b30c14 --- /dev/null +++ b/apps/roam/src/components/settings/components/EphemeralBlocksPanel.tsx @@ -0,0 +1,107 @@ +import React, { useRef, useEffect, useCallback } from "react"; +import { Label } from "@blueprintjs/core"; +import Description from "roamjs-components/components/Description"; +import createBlock from "roamjs-components/writes/createBlock"; +import getFullTreeByParentUid from "roamjs-components/queries/getFullTreeByParentUid"; +import getFirstChildUidByBlockUid from "roamjs-components/queries/getFirstChildUidByBlockUid"; +import type { TreeNode } from "roamjs-components/types"; +import type { RoamNodeType } from "../utils/zodSchema"; +import { setDiscourseNodeSetting } from "../utils/accessors"; +import type { DiscourseNodeBaseProps } from "./BlockPropSettingPanels"; + +const DEBOUNCE_MS = 250; + +type DualWriteBlocksPanelProps = DiscourseNodeBaseProps & { + uid: string; +}; + +const serializeBlockTree = (children: TreeNode[]): RoamNodeType[] => + children + .sort((a, b) => a.order - b.order) + .map((child) => ({ + text: child.text, + ...(child.children.length > 0 && { + children: serializeBlockTree(child.children), + }), + })); + +const DualWriteBlocksPanel = ({ + nodeType, + settingKeys, + title, + description, + uid, +}: DualWriteBlocksPanelProps) => { + const containerRef = useRef(null); + const debounceRef = useRef(0); + const pullWatchArgsRef = useRef< + [string, string, (before: unknown, after: unknown) => void] | null + >(null); + + const handleChange = useCallback(() => { + window.clearTimeout(debounceRef.current); + debounceRef.current = window.setTimeout(() => { + const tree = getFullTreeByParentUid(uid); + const serialized = serializeBlockTree(tree.children); + setDiscourseNodeSetting(nodeType, settingKeys, serialized); + }, DEBOUNCE_MS); + }, [uid, nodeType, settingKeys]); + + useEffect(() => { + const el = containerRef.current; + if (!el) return; + + if (!getFirstChildUidByBlockUid(uid)) { + void createBlock({ node: { text: " " }, parentUid: uid }).then(() => { + el.innerHTML = ""; + void window.roamAlphaAPI.ui.components.renderBlock({ uid, el }); + }); + } else { + el.innerHTML = ""; + void window.roamAlphaAPI.ui.components.renderBlock({ uid, el }); + } + + const pattern = "[:block/string {:block/children ...}]"; + const entityId = `[:block/uid "${uid}"]`; + const callback = () => handleChange(); + pullWatchArgsRef.current = [pattern, entityId, callback]; + window.roamAlphaAPI.data.addPullWatch(pattern, entityId, callback); + + return () => { + window.clearTimeout(debounceRef.current); + if (pullWatchArgsRef.current) { + window.roamAlphaAPI.data.removePullWatch(...pullWatchArgsRef.current); + pullWatchArgsRef.current = null; + } + }; + }, [uid, handleChange]); + + return ( + <> + + +
+ + ); +}; + +export default DualWriteBlocksPanel; diff --git a/apps/roam/src/components/settings/utils/zodSchema.example.ts b/apps/roam/src/components/settings/utils/zodSchema.example.ts index ee9187912..544055b4f 100644 --- a/apps/roam/src/components/settings/utils/zodSchema.example.ts +++ b/apps/roam/src/components/settings/utils/zodSchema.example.ts @@ -30,10 +30,6 @@ const canvasSettings: CanvasSettings = { }; const suggestiveRules: SuggestiveRules = { - template: [ - { text: "Summary::", heading: 2 }, - { text: "Key Points::", heading: 2, children: [{ text: "" }] }, - ], embeddingRef: "((block-uid-123))", isFirstChild: true, }; diff --git a/apps/roam/src/components/settings/utils/zodSchema.ts b/apps/roam/src/components/settings/utils/zodSchema.ts index 3c354f8df..76be0a5d7 100644 --- a/apps/roam/src/components/settings/utils/zodSchema.ts +++ b/apps/roam/src/components/settings/utils/zodSchema.ts @@ -75,7 +75,6 @@ export const RoamNodeSchema: z.ZodType = z.lazy(() => ); export const SuggestiveRulesSchema = z.object({ - template: z.array(RoamNodeSchema).default([]), embeddingRef: z.string().default(""), isFirstChild: z.boolean().default(false), }); From 65a737be8e48e204df86edc6cbccd31bf49edb3d Mon Sep 17 00:00:00 2001 From: sid597 Date: Sun, 15 Feb 2026 14:41:17 +0530 Subject: [PATCH 2/5] fix bug --- .../src/components/settings/DiscourseNodeSuggestiveRules.tsx | 4 +++- apps/roam/src/components/settings/NodeConfig.tsx | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/roam/src/components/settings/DiscourseNodeSuggestiveRules.tsx b/apps/roam/src/components/settings/DiscourseNodeSuggestiveRules.tsx index f18fd2a3d..9a5fac825 100644 --- a/apps/roam/src/components/settings/DiscourseNodeSuggestiveRules.tsx +++ b/apps/roam/src/components/settings/DiscourseNodeSuggestiveRules.tsx @@ -12,6 +12,8 @@ import { DiscourseNodeTextPanel, } from "./components/BlockPropSettingPanels"; +const TEMPLATE_SETTING_KEYS = ["template"]; + const BlockRenderer = ({ uid }: { uid: string }) => { const containerRef = useRef(null); @@ -85,7 +87,7 @@ const DiscourseNodeSuggestiveRules = ({ nodeType={node.type} title="Template" description={`The template that auto fills ${node.text} page when generated.`} - settingKeys={["template"]} + settingKeys={TEMPLATE_SETTING_KEYS} uid={templateUid} /> diff --git a/apps/roam/src/components/settings/NodeConfig.tsx b/apps/roam/src/components/settings/NodeConfig.tsx index ca615eef4..0c0e5ff7d 100644 --- a/apps/roam/src/components/settings/NodeConfig.tsx +++ b/apps/roam/src/components/settings/NodeConfig.tsx @@ -27,6 +27,8 @@ import { DiscourseNodeFlagPanel, } from "./components/BlockPropSettingPanels"; +const TEMPLATE_SETTING_KEYS = ["template"]; + export const getCleanTagText = (tag: string): string => { return tag.replace(/^#+/, "").trim().toUpperCase(); }; @@ -346,7 +348,7 @@ const NodeConfig = ({ nodeType={node.type} title="Template" description={`The template that auto fills ${node.text} page when generated.`} - settingKeys={["template"]} + settingKeys={TEMPLATE_SETTING_KEYS} uid={templateUid} />
From 56726a9ca7e9793f51bfbd749cb06a681ac4296d Mon Sep 17 00:00:00 2001 From: sid597 Date: Sun, 15 Feb 2026 22:47:40 +0530 Subject: [PATCH 3/5] address review --- .../src/components/settings/components/EphemeralBlocksPanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/roam/src/components/settings/components/EphemeralBlocksPanel.tsx b/apps/roam/src/components/settings/components/EphemeralBlocksPanel.tsx index 0d8b30c14..86d9528c2 100644 --- a/apps/roam/src/components/settings/components/EphemeralBlocksPanel.tsx +++ b/apps/roam/src/components/settings/components/EphemeralBlocksPanel.tsx @@ -61,7 +61,7 @@ const DualWriteBlocksPanel = ({ void window.roamAlphaAPI.ui.components.renderBlock({ uid, el }); } - const pattern = "[:block/string {:block/children ...}]"; + const pattern = "[:block/string :block/order {:block/children ...}]"; const entityId = `[:block/uid "${uid}"]`; const callback = () => handleChange(); pullWatchArgsRef.current = [pattern, entityId, callback]; From 002ca3590bcf331ac11ad382c04b82105e57a3b7 Mon Sep 17 00:00:00 2001 From: sid597 Date: Mon, 23 Feb 2026 19:42:51 +0530 Subject: [PATCH 4/5] address review --- .../components/EphemeralBlocksPanel.tsx | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/apps/roam/src/components/settings/components/EphemeralBlocksPanel.tsx b/apps/roam/src/components/settings/components/EphemeralBlocksPanel.tsx index 86d9528c2..1b0c3899b 100644 --- a/apps/roam/src/components/settings/components/EphemeralBlocksPanel.tsx +++ b/apps/roam/src/components/settings/components/EphemeralBlocksPanel.tsx @@ -20,6 +20,8 @@ const serializeBlockTree = (children: TreeNode[]): RoamNodeType[] => .sort((a, b) => a.order - b.order) .map((child) => ({ text: child.text, + ...(child.heading && { heading: child.heading as 0 | 1 | 2 | 3 }), + ...(child.open === false && { open: false }), ...(child.children.length > 0 && { children: serializeBlockTree(child.children), }), @@ -49,24 +51,29 @@ const DualWriteBlocksPanel = ({ useEffect(() => { const el = containerRef.current; - if (!el) return; + if (!el || !uid) return; + + const pattern = "[:block/string :block/order {:block/children ...}]"; + const entityId = `[:block/uid "${uid}"]`; + const callback = () => handleChange(); + + const registerPullWatch = () => { + pullWatchArgsRef.current = [pattern, entityId, callback]; + window.roamAlphaAPI.data.addPullWatch(pattern, entityId, callback); + }; if (!getFirstChildUidByBlockUid(uid)) { void createBlock({ node: { text: " " }, parentUid: uid }).then(() => { el.innerHTML = ""; void window.roamAlphaAPI.ui.components.renderBlock({ uid, el }); + registerPullWatch(); }); } else { el.innerHTML = ""; void window.roamAlphaAPI.ui.components.renderBlock({ uid, el }); + registerPullWatch(); } - const pattern = "[:block/string :block/order {:block/children ...}]"; - const entityId = `[:block/uid "${uid}"]`; - const callback = () => handleChange(); - pullWatchArgsRef.current = [pattern, entityId, callback]; - window.roamAlphaAPI.data.addPullWatch(pattern, entityId, callback); - return () => { window.clearTimeout(debounceRef.current); if (pullWatchArgsRef.current) { From a449e30b0c65fc4532f5c566e68edc20ce0b3537 Mon Sep 17 00:00:00 2001 From: sid597 Date: Tue, 24 Feb 2026 11:34:10 +0530 Subject: [PATCH 5/5] address review --- .../components/EphemeralBlocksPanel.tsx | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/apps/roam/src/components/settings/components/EphemeralBlocksPanel.tsx b/apps/roam/src/components/settings/components/EphemeralBlocksPanel.tsx index 1b0c3899b..92dead099 100644 --- a/apps/roam/src/components/settings/components/EphemeralBlocksPanel.tsx +++ b/apps/roam/src/components/settings/components/EphemeralBlocksPanel.tsx @@ -5,8 +5,8 @@ import createBlock from "roamjs-components/writes/createBlock"; import getFullTreeByParentUid from "roamjs-components/queries/getFullTreeByParentUid"; import getFirstChildUidByBlockUid from "roamjs-components/queries/getFirstChildUidByBlockUid"; import type { TreeNode } from "roamjs-components/types"; -import type { RoamNodeType } from "../utils/zodSchema"; -import { setDiscourseNodeSetting } from "../utils/accessors"; +import type { RoamNodeType } from "~/components/settings/utils/zodSchema"; +import { setDiscourseNodeSetting } from "~/components/settings/utils/accessors"; import type { DiscourseNodeBaseProps } from "./BlockPropSettingPanels"; const DEBOUNCE_MS = 250; @@ -89,23 +89,18 @@ const DualWriteBlocksPanel = ({ {title} -
);