Skip to content

[ENG-1547] Relation creation via drag handle (Obsidian)#909

Open
trangdoan982 wants to merge 10 commits intomainfrom
eng-1547-implement-relation-creation-via-drag-handles-in-tldraw
Open

[ENG-1547] Relation creation via drag handle (Obsidian)#909
trangdoan982 wants to merge 10 commits intomainfrom
eng-1547-implement-relation-creation-via-drag-handles-in-tldraw

Conversation

@trangdoan982
Copy link
Copy Markdown
Member

@trangdoan982 trangdoan982 commented Mar 23, 2026

https://www.loom.com/share/ee0d603ff9db4025a1c8f857f2b01aec

Summary

  • Clicking an existing persisted relation arrow now shows the relation type dropdown at the arrow midpoint
  • Selecting a new type updates the arrow visuals (color, label) and persists the change to relations.json in-place (preserving id, created, author, etc.)
  • Added updateRelationType() helper to relationsStore.ts
  • Handles edge cases: same-type re-selection dismisses without changes, Escape/click-outside closes without modifying, dropdown doesn't re-open after creation or edit flows

Test plan

  • Create a relation via drag handles — existing flow still works, dropdown closes after selection
  • Select a new type — arrow updates (color, label) and relations.json type field updated in-place
  • Escape/click outside — dropdown closes, arrow disappear
  • Relation Overlay updated correctly after relation is created
image

🤖 Generated with Claude Code


Open with Devin

@linear
Copy link
Copy Markdown

linear bot commented Mar 23, 2026

@supabase
Copy link
Copy Markdown

supabase bot commented Mar 23, 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 ↗︎.

devin-ai-integration[bot]

This comment was marked as resolved.

@trangdoan982 trangdoan982 force-pushed the eng-1547-implement-relation-creation-via-drag-handles-in-tldraw branch from 75c8668 to 0ebe9ce Compare March 26, 2026 03:47
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

graphite-app[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@trangdoan982 trangdoan982 changed the title [ENG-1547] Add relation type editing via click on existing arrows [ENG-1547] Relation creation via drag handle (Obsidian) Mar 27, 2026
@trangdoan982 trangdoan982 requested a review from mdroidian March 27, 2026 19:43
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.

  1. CI is failing.

  2. This PR is ~50% over the line change limit. I understand this might be required (or ideal) in this case, that being said, would you mind creating a Loom video walking through this code and code choices?

The biggest concern I have doing a quick review was the code possibly not being as DRY as possible. I worry we might be duplicating work, specifically in the relation/arrow drag handling logic and potential relations checking.

devin-ai-integration[bot]

This comment was marked as resolved.

@trangdoan982 trangdoan982 requested a review from mdroidian April 6, 2026 17:05
@trangdoan982
Copy link
Copy Markdown
Member Author

@mdroidian video explain file structure here: https://youtu.be/BBAKm1G-zXM

Comment on lines 1119 to 1128
targetNodeTypeId: string,
relationTypeId: string,
): boolean {
const plugin = this.options.plugin;

// Check direct connection (source -> target)
const directConnection = plugin.settings.discourseRelations.some(
(relation) =>
relation.relationshipTypeId === relationTypeId &&
relation.sourceId === sourceNodeTypeId &&
relation.destinationId === targetNodeTypeId,
);

if (directConnection) return true;

// Check reverse connection (target -> source)
// This handles bidirectional relations where the complement is used
const reverseConnection = plugin.settings.discourseRelations.some(
(relation) =>
relation.relationshipTypeId === relationTypeId &&
relation.sourceId === targetNodeTypeId &&
relation.destinationId === sourceNodeTypeId,
);

return reverseConnection;
return isValidRelationConnection({
discourseRelations: this.options.plugin.settings.discourseRelations,
relationTypeId,
sourceNodeTypeId,
targetNodeTypeId,
});
}
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.

(Refers to lines 1117-1128)

📝 Info: isValidNodeConnection method is now dead code

The isValidNodeConnection method on DiscourseRelationUtil (DiscourseRelationShape.tsx:1117-1128) was refactored to delegate to the new isValidRelationConnection utility. However, its only call site in onHandleDrag was changed to call isValidRelationConnection directly (DiscourseRelationShape.tsx:349). A search confirms isValidNodeConnection has no remaining callers anywhere in the codebase. It could be removed to reduce confusion.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

(Refers to lines 139-143)

📝 Info: Dead code: unreachable !target branch in DiscourseRelationTool

At line 139, the check if (!target) is unreachable because target was already validated as non-null at line 110 (if (!target || target.type !== 'discourse-node')), which returns early if null. This means this.createArrowShape() on line 140 can never be called from this branch. This is a pre-existing issue (not introduced by this PR), but since the surrounding code was modified by the refactoring, it's worth noting.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

Nothing blocking approval from me, but this looks like a good candidate for a small DRY follow-up later. DragHandleOverlay seems to reimplement part of the existing relation-arrow flow that already lives in DiscourseRelationTool / DiscourseRelationUtil.onHandleDrag, especially around > arrow creation, end-handle binding updates, and finalization/reification. Not suggesting we hold this PR for it, just flagging it as a worthwhile cleanup > target.

There was a couple interesting Devin comments that might be worth looking at as well.

);

// Auto-dismiss if arrow is deleted
useEffect(() => {
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.

This is worrisome. What case is this handling that cannot be handled directly via a user event? EG: on escape and on clickoutside are already handled.

);

// Handle click outside
useEffect(() => {
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 the click/escape be handled with onBlur on the dropdown itself?

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