Skip to content

ENG-1884 Add 'convert to' node option to right click menu for tldraw shape to allow user to convert shape to node (Roam) - #1431

Open
trangdoan982 wants to merge 1 commit into
eng-1356-implement-image-to-node-conversion-flow-via-icon-button-infrom
eng-1884-add-convert-to-node-option-to-right-click-menu-for-tldraw
Open

ENG-1884 Add 'convert to' node option to right click menu for tldraw shape to allow user to convert shape to node (Roam)#1431
trangdoan982 wants to merge 1 commit into
eng-1356-implement-image-to-node-conversion-flow-via-icon-button-infrom
eng-1884-add-convert-to-node-option-to-right-click-menu-for-tldraw

Conversation

@trangdoan982

@trangdoan982 trangdoan982 commented Sep 10, 2026

Copy link
Copy Markdown
Member

https://www.loom.com/share/14e6c9d2102b49349869f7afd55a00de

The canvas already had a Convert To submenu, gated to a single text or image shape. Sticky notes and labelled rectangles hit the return () => {} fallback in getOnSelectForShape and did nothing. This widens the gate to geo and note, and routes all three call sites through one helper so the right-click menu and the ?C dialog cannot drift apart.

Stacked on #1427 (ENG-1356), which extracted replaceShapeWithDiscourseNode. #1427 must merge first. Base is that branch, so this diff is only the 24 added lines.

Reviewer brief

  • Result: right-clicking a sticky note or a rectangle with text offers Convert To, prefilled with the shape's text. Empty ones do not. text and image behave as before.
  • Review focus: apps/roam/src/components/canvas/convertShapeToDiscourseNode.tsgetConvertibleShapeText returns the dialog's initial text, or null when the shape cannot convert. Every gate is now === null on that one value, replacing the isTextSelected || isImageSelected booleans that existed in two files.
  • Risk: no live-graph run. See Verification.

Decisions worth challenging

  • Empty geo and note shapes are excluded. FEE-824: convert tldraw shape to discourse node (if it has text in it) asks for conversion "if it has text in it",
  • Arrows are excluded even though TLArrowShapeProps carries text. Arrows already convert to relations
  • isImageSelected survives in both files. It no longer gates the submenu, but it still filters page-node out of the type list, which is about images lacking a key-image flag rather than about convertibility.
  • "text" in shape.props rather than a cast. It narrows the props union, so shape.props.text types as string with no as and no optional chaining. Same idiom as defaultHandleExternalTextContent.ts:79. The preceding TEXT_SHAPE_TYPES check is the policy allow-list, not a redundant guard: it is what keeps arrows out.

🤖 Generated with Claude Code

The canvas Convert To submenu and its ?C dialog only accepted text and
image shapes, so sticky notes and labelled rectangles fell through to a
no-op. Both gates now call one helper, getConvertibleShapeText, which
returns the dialog's initial text or null when the shape cannot convert.
Geo and note shapes qualify only when they carry text.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Sep 10, 2026

Copy link
Copy Markdown

ENG-1884

@vercel

vercel Bot commented Sep 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
discourse-graph Skipped Skipped Sep 10, 2026 11:51pm UTC

Request Review

@supabase

supabase Bot commented Sep 10, 2026

Copy link
Copy Markdown

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 ↗︎.

@mdroidian

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-11T06:16:08.211711Z a976b32 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a976b32780

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (shape.type === "image") return "";
if (!TEXT_SHAPE_TYPES.includes(shape.type)) return null;
if (!("text" in shape.props)) return null;
const text = shape.props.text.trim();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the original shape text after the emptiness check

When a text, note, or geo shape begins or ends with whitespace—for example, an intentionally indented multiline snippet—this trimmed value is passed to the node dialog, so conversion silently changes the shape's content and existing text shapes no longer behave as before. Use the trimmed value only to decide whether a note or geo shape is empty, while returning the original shape.props.text.

Useful? React with 👍 / 👎.

};
}
return () => {};
return () => openDialogAndCreateShape({ initialText: shapeText });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the parent when converting nested shapes

When one of the newly supported geo or note shapes is inside a frame or group, this route eventually calls replaceShapeWithDiscourseNode, which copies only the shape's local x/y and creates the replacement without its parentId. Tldraw then interprets those coordinates in page or newly inferred parent space, so the replacement can move far from the original or leave its container; pass the original parent and parent-relative transform through the replacement.

Useful? React with 👍 / 👎.

@mdroidian mdroidian left a comment

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.

Could you run $dg-delegated-full-review and also address the codex comments? Thanks!

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