From e5876cc62fa918a6a84d65d3d569f0327a581754 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Sun, 22 Mar 2026 16:24:48 -0400 Subject: [PATCH 1/4] [ENG-1545] Switch positions of node type and node title fields Move Content input above Node Type selector so users can immediately start typing. Support empty Node Type (queries all node types), auto-detect type on selection, and lock type when a node is selected. Co-Authored-By: Claude Opus 4.6 --- apps/roam/src/components/ModifyNodeDialog.tsx | 115 +++++++++++++----- .../utils/registerCommandPaletteCommands.ts | 12 +- 2 files changed, 86 insertions(+), 41 deletions(-) diff --git a/apps/roam/src/components/ModifyNodeDialog.tsx b/apps/roam/src/components/ModifyNodeDialog.tsx index dafd67f07..45a68403f 100644 --- a/apps/roam/src/components/ModifyNodeDialog.tsx +++ b/apps/roam/src/components/ModifyNodeDialog.tsx @@ -109,13 +109,16 @@ const ModifyNodeDialog = ({ : allNodes.filter(excludeDefaultNodes); }, [includeDefaultNodes]); - const [selectedNodeType, setSelectedNodeType] = useState(() => { + const [selectedNodeType, setSelectedNodeType] = useState< + (typeof discourseNodes)[number] | null + >(() => { + if (!nodeType) return null; const node = discourseNodes.find((n) => n.type === nodeType); - return node || discourseNodes[0]; + return node || null; }); const nodeFormat = useMemo(() => { - return selectedNodeType.format || ""; + return selectedNodeType?.format || ""; }, [selectedNodeType]); const referencedNode = useMemo(() => { @@ -160,6 +163,39 @@ const ModifyNodeDialog = ({ if (contentRequestIdRef.current === req && alive) { setOptions((prev) => ({ ...prev, content: results })); } + } else { + // Query all discourse node types in parallel + const allResults = await Promise.all( + discourseNodes.map(async (node) => { + const conditionUid = window.roamAlphaAPI.util.generateUID(); + const results = await fireQuery({ + returnNode: "node", + selections: [], + conditions: [ + { + source: "node", + relation: "is a", + target: node.type, + uid: conditionUid, + type: "clause", + }, + ], + }); + return results.map((r) => ({ + ...r, + _discourseNodeType: node.type, + })); + }), + ); + const seen = new Set(); + const deduped = allResults.flat().filter((r) => { + if (seen.has(r.uid)) return false; + seen.add(r.uid); + return true; + }); + if (contentRequestIdRef.current === req && alive) { + setOptions((prev) => ({ ...prev, content: deduped })); + } } } catch (error) { if (contentRequestIdRef.current === req && alive) { @@ -226,9 +262,20 @@ const ModifyNodeDialog = ({ }; }, [selectedNodeType, referencedNode]); - const setValue = useCallback((r: Result) => { - setContent(r); - }, []); + const setValue = useCallback( + (r: Result) => { + setContent(r); + if (!selectedNodeType && r.uid) { + const detectedType = (r as Record) + ._discourseNodeType as string | undefined; + if (detectedType) { + const nt = discourseNodes.find((n) => n.type === detectedType); + if (nt) setSelectedNodeType(nt); + } + } + }, + [selectedNodeType, discourseNodes], + ); const setReferencedNodeValueCallback = useCallback((r: Result) => { setReferencedNodeValue(r); @@ -304,9 +351,13 @@ const ModifyNodeDialog = ({ const onSubmit = async () => { if (!content.text.trim()) return; + if (!selectedNodeType && !isContentLocked) { + setError("Please select a node type"); + return; + } posthog.capture("Modify Node Dialog: Submit Triggered", { mode, - nodeType: selectedNodeType.type, + nodeType: selectedNodeType?.type, }); try { if (mode === "create") { @@ -326,7 +377,7 @@ const ModifyNodeDialog = ({ await addImageToPage({ pageUid, imageUrl, - configPageUid: selectedNodeType.type, + configPageUid: selectedNodeType!.type, extensionAPI, }); } @@ -373,7 +424,7 @@ const ModifyNodeDialog = ({ } else { formattedTitle = await getNewDiscourseNodeText({ text: content.text.trim(), - nodeType: selectedNodeType.type, + nodeType: selectedNodeType!.type, blockUid: sourceBlockUid, }); } @@ -384,7 +435,7 @@ const ModifyNodeDialog = ({ // Create new discourse node const newPageUid = await createDiscourseNode({ text: formattedTitle, - configPageUid: selectedNodeType.type, + configPageUid: selectedNodeType!.type, extensionAPI, imageUrl, }); @@ -505,6 +556,26 @@ const ModifyNodeDialog = ({ style={{ pointerEvents: "all" }} >
+ {/* Content Input */} +
+ + +
+ {/* Node Type Selector */}
- {/* Content Input */} -
- - -
- {/* Referenced Node Input */} {referencedNode && !isContentLocked && mode === "create" && (
diff --git a/apps/roam/src/utils/registerCommandPaletteCommands.ts b/apps/roam/src/utils/registerCommandPaletteCommands.ts index 6e5dbf61e..2eb143229 100644 --- a/apps/roam/src/utils/registerCommandPaletteCommands.ts +++ b/apps/roam/src/utils/registerCommandPaletteCommands.ts @@ -189,19 +189,9 @@ export const registerCommandPaletteCommands = (onloadArgs: OnloadArgs) => { const selectionStart = uid ? getSelectionStartForBlock(uid) : 0; - const defaultNodeType = - getDiscourseNodes().filter(excludeDefaultNodes)[0]?.type; - if (!defaultNodeType) { - renderToast({ - id: "create-discourse-node-command-no-types", - content: "No discourse node types found in settings.", - }); - return; - } - renderModifyNodeDialog({ mode: "create", - nodeType: defaultNodeType, + nodeType: "", initialValue: { text: "", uid: "" }, extensionAPI, onSuccess: async (result) => { From f3d463021f9ca7640bafc1d11a0f93e9a2b56d18 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Fri, 27 Mar 2026 16:29:33 -0400 Subject: [PATCH 2/4] lint and remove unnecessary change --- apps/roam/src/components/ModifyNodeDialog.tsx | 2 +- .../roam/src/utils/registerCommandPaletteCommands.ts | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/roam/src/components/ModifyNodeDialog.tsx b/apps/roam/src/components/ModifyNodeDialog.tsx index 45a68403f..813155129 100644 --- a/apps/roam/src/components/ModifyNodeDialog.tsx +++ b/apps/roam/src/components/ModifyNodeDialog.tsx @@ -183,7 +183,7 @@ const ModifyNodeDialog = ({ }); return results.map((r) => ({ ...r, - _discourseNodeType: node.type, + discourseNodeType: node.type, })); }), ); diff --git a/apps/roam/src/utils/registerCommandPaletteCommands.ts b/apps/roam/src/utils/registerCommandPaletteCommands.ts index 2eb143229..6e5dbf61e 100644 --- a/apps/roam/src/utils/registerCommandPaletteCommands.ts +++ b/apps/roam/src/utils/registerCommandPaletteCommands.ts @@ -189,9 +189,19 @@ export const registerCommandPaletteCommands = (onloadArgs: OnloadArgs) => { const selectionStart = uid ? getSelectionStartForBlock(uid) : 0; + const defaultNodeType = + getDiscourseNodes().filter(excludeDefaultNodes)[0]?.type; + if (!defaultNodeType) { + renderToast({ + id: "create-discourse-node-command-no-types", + content: "No discourse node types found in settings.", + }); + return; + } + renderModifyNodeDialog({ mode: "create", - nodeType: "", + nodeType: defaultNodeType, initialValue: { text: "", uid: "" }, extensionAPI, onSuccess: async (result) => { From 91cec862cf9ed98b7c592e0cf871ca72403d0cfe Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Fri, 27 Mar 2026 16:51:35 -0400 Subject: [PATCH 3/4] small fixes --- apps/roam/src/components/ModifyNodeDialog.tsx | 10 +++++++--- .../roam/src/utils/registerCommandPaletteCommands.ts | 12 +----------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/apps/roam/src/components/ModifyNodeDialog.tsx b/apps/roam/src/components/ModifyNodeDialog.tsx index 813155129..37dcfa86a 100644 --- a/apps/roam/src/components/ModifyNodeDialog.tsx +++ b/apps/roam/src/components/ModifyNodeDialog.tsx @@ -260,17 +260,20 @@ const ModifyNodeDialog = ({ alive = false; refAlive = false; }; - }, [selectedNodeType, referencedNode]); + }, [selectedNodeType, referencedNode, discourseNodes]); const setValue = useCallback( (r: Result) => { setContent(r); if (!selectedNodeType && r.uid) { const detectedType = (r as Record) - ._discourseNodeType as string | undefined; + .discourseNodeType as string | undefined; if (detectedType) { const nt = discourseNodes.find((n) => n.type === detectedType); - if (nt) setSelectedNodeType(nt); + if (nt) { + setSelectedNodeType(nt); + setError(""); + } } } }, @@ -591,6 +594,7 @@ const ModifyNodeDialog = ({ if (nt) { setSelectedNodeType(nt); setReferencedNodeValue({ text: "", uid: "" }); + setError(""); } }} disabled={ diff --git a/apps/roam/src/utils/registerCommandPaletteCommands.ts b/apps/roam/src/utils/registerCommandPaletteCommands.ts index 6e5dbf61e..2eb143229 100644 --- a/apps/roam/src/utils/registerCommandPaletteCommands.ts +++ b/apps/roam/src/utils/registerCommandPaletteCommands.ts @@ -189,19 +189,9 @@ export const registerCommandPaletteCommands = (onloadArgs: OnloadArgs) => { const selectionStart = uid ? getSelectionStartForBlock(uid) : 0; - const defaultNodeType = - getDiscourseNodes().filter(excludeDefaultNodes)[0]?.type; - if (!defaultNodeType) { - renderToast({ - id: "create-discourse-node-command-no-types", - content: "No discourse node types found in settings.", - }); - return; - } - renderModifyNodeDialog({ mode: "create", - nodeType: defaultNodeType, + nodeType: "", initialValue: { text: "", uid: "" }, extensionAPI, onSuccess: async (result) => { From 91e1f57836d8695f3046b2091a7ab8b2c0344385 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Fri, 27 Mar 2026 20:47:02 -0400 Subject: [PATCH 4/4] address pr comment --- apps/roam/src/components/ModifyNodeDialog.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/roam/src/components/ModifyNodeDialog.tsx b/apps/roam/src/components/ModifyNodeDialog.tsx index 37dcfa86a..16a768cee 100644 --- a/apps/roam/src/components/ModifyNodeDialog.tsx +++ b/apps/roam/src/components/ModifyNodeDialog.tsx @@ -380,7 +380,7 @@ const ModifyNodeDialog = ({ await addImageToPage({ pageUid, imageUrl, - configPageUid: selectedNodeType!.type, + configPageUid: selectedNodeType?.type || "", extensionAPI, }); } @@ -602,7 +602,7 @@ const ModifyNodeDialog = ({ } popoverProps={{ openOnTargetFocus: false }} className={ - mode === "edit" || disableNodeTypeChange + mode === "edit" || disableNodeTypeChange || isContentLocked ? "cursor-not-allowed opacity-50" : "" }