Skip to content

[ENG-1546] Relation creation via drag handles (Roam)#923

Merged
trangdoan982 merged 6 commits intomainfrom
eng-1546-implement-relation-creation-via-drag-handles-in-tldraw-roam
Apr 20, 2026
Merged

[ENG-1546] Relation creation via drag handles (Roam)#923
trangdoan982 merged 6 commits intomainfrom
eng-1546-implement-relation-creation-via-drag-handles-in-tldraw-roam

Conversation

@trangdoan982
Copy link
Copy Markdown
Member

@trangdoan982 trangdoan982 commented Mar 28, 2026

https://www.loom.com/share/253732c148ca4cb39907c564e6b53e77

Summary

  • Adds drag handle dots on selected discourse nodes (4 edge midpoints) for creating relations by dragging to another node
  • Shows a relation type dropdown at the midpoint between source/target nodes after a successful drag
  • On type selection, creates the relation shape with correct type and bindings, and persists via handleCreateRelationsInRoam (supports both stored/reified relations and legacy triple-based approach)
  • Adapted from the Obsidian implementation ([ENG-1547] Relation creation via drag handle (Obsidian) #909) to fit Roam's per-relation-type shape architecture — uses SVG overlay line during drag instead of temporary tldraw shapes

Test plan

  • Select a single discourse node — 4 drag handle dots appear at edge midpoints
  • Drag from a handle to another discourse node — SVG line follows cursor, turns blue on hover over valid target
  • Release on a valid target — relation type dropdown appears at midpoint
  • Select a relation type — arrow shape created with correct color/label, relation persisted (check stored relations page or triple blocks)
  • Release on empty space — toast warning, no arrow created
  • Release on a node with no valid relation types — toast warning, no arrow created
  • Escape or click outside dropdown — dropdown dismissed, no arrow created
  • Handles reappear when selecting a different discourse node after any flow

🤖 Generated with Claude Code


Open with Devin

@linear
Copy link
Copy Markdown

linear Bot commented Mar 28, 2026

@supabase
Copy link
Copy Markdown

supabase Bot commented Mar 28, 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 ↗︎.

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 bugs or issues to report.

Open in Devin Review

devin-ai-integration[bot]

This comment was marked as resolved.

@trangdoan982 trangdoan982 force-pushed the eng-1546-implement-relation-creation-via-drag-handles-in-tldraw-roam branch from 1565d15 to e46db0a Compare April 8, 2026 19:33
trangdoan982 and others added 5 commits April 8, 2026 15:45
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Matches the Obsidian (eng-1547) pattern: padding is now applied after
pageToViewport conversion so dots remain a fixed screen distance from
the node edge regardless of zoom level.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Show complement label when creating relation in reverse direction
- DRY checkConnectionType into canvasUtils (used by RelationUtil + overlays)
- Remove labelColor override to match DiscourseRelationTool convention

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@trangdoan982 trangdoan982 requested a review from mdroidian April 16, 2026 14:21
Comment thread apps/roam/src/components/canvas/overlays/DragHandleOverlay.tsx
Comment thread apps/roam/src/components/canvas/overlays/DragHandleOverlay.tsx Outdated
Comment thread apps/roam/src/components/canvas/overlays/DragHandleOverlay.tsx
Comment thread apps/roam/src/components/canvas/overlays/RelationTypeDropdown.tsx
Comment thread apps/roam/src/components/canvas/overlays/RelationTypeDropdown.tsx Outdated
Comment thread apps/roam/src/components/canvas/overlays/RelationTypeDropdown.tsx Outdated
Comment thread apps/roam/src/components/canvas/overlays/RelationTypeDropdown.tsx Outdated
Comment thread apps/roam/src/components/canvas/overlays/RelationTypeDropdown.tsx Outdated
@trangdoan982 trangdoan982 force-pushed the eng-1546-implement-relation-creation-via-drag-handles-in-tldraw-roam branch from 842d51d to 612a8f9 Compare April 20, 2026 22:23
@trangdoan982 trangdoan982 merged commit 114ed85 into main Apr 20, 2026
8 checks passed
Comment on lines +272 to +278
const sourceNode = editor.getShape(pending.sourceId);
const targetNode = editor.getShape(pending.targetId);
const { isReverse } = checkConnectionType(
selectedRelation,
sourceNode?.type ?? "",
targetNode?.type ?? "",
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If either the source or target node is deleted between the drop and relation type selection, sourceNode or targetNode will be null/undefined. The code uses nullish coalescing to default to empty strings (sourceNode?.type ?? ""), but doesn't validate that both nodes still exist before proceeding.

This will cause checkConnectionType to return { isDirect: false, isReverse: false } for empty string types, potentially creating the relation with the wrong label (using primary label instead of complement).

Fix: Add validation after getting the shapes:

const sourceNode = editor.getShape(pending.sourceId);
const targetNode = editor.getShape(pending.targetId);
if (!sourceNode || !targetNode || !isDiscourseNodeShape(editor, sourceNode) || !isDiscourseNodeShape(editor, targetNode)) {
  setPending(null);
  sourceNodeRef.current = null;
  return;
}

const { isReverse } = checkConnectionType(
  selectedRelation,
  sourceNode.type,
  targetNode.type,
);
Suggested change
const sourceNode = editor.getShape(pending.sourceId);
const targetNode = editor.getShape(pending.targetId);
const { isReverse } = checkConnectionType(
selectedRelation,
sourceNode?.type ?? "",
targetNode?.type ?? "",
);
const sourceNode = editor.getShape(pending.sourceId);
const targetNode = editor.getShape(pending.targetId);
if (
!sourceNode ||
!targetNode ||
!isDiscourseNodeShape(editor, sourceNode) ||
!isDiscourseNodeShape(editor, targetNode)
) {
setPending(null);
sourceNodeRef.current = null;
return;
}
const { isReverse } = checkConnectionType(
selectedRelation,
sourceNode.type,
targetNode.type,
);

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@trangdoan982 trangdoan982 deleted the eng-1546-implement-relation-creation-via-drag-handles-in-tldraw-roam branch April 20, 2026 22:29
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