Skip to content

[ENG-1368] Drag and drop links from Base and File explorer into canvas#780

Open
trangdoan982 wants to merge 2 commits intomainfrom
eng-1368-drag-drop-links
Open

[ENG-1368] Drag and drop links from Base and File explorer into canvas#780
trangdoan982 wants to merge 2 commits intomainfrom
eng-1368-drag-drop-links

Conversation

@trangdoan982
Copy link
Collaborator

@trangdoan982 trangdoan982 commented Feb 13, 2026

@linear
Copy link

linear bot commented Feb 13, 2026

@supabase
Copy link

supabase bot commented Feb 13, 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
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 found 1 potential issue.

View 6 additional findings in Devin Review.

Open in Devin Review

…ers.ts

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Comment on lines +270 to +290
editor.registerExternalContentHandler("url", (externalContent) => {
void handleExternalUrlContent({
editor,
url: externalContent.url,
point: externalContent.point,
plugin,
canvasFile: file,
defaultHandlerOpts: {
toasts: {
addToast: (t: Omit<TLUiToast, "id"> & { id?: string }) => {
new Notice(t.description ?? t.title ?? "Error");
return "";
},
removeToast: () => "",
clearToasts: () => {},
toasts: { get: () => [], update: () => {} },
},
msg: (key?: string) => key ?? "",
} as unknown as TLDefaultExternalContentHandlerOpts,
});
});
Copy link
Contributor

Choose a reason for hiding this comment

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

The void keyword is used with an async function inside a handler, which means unhandled promise rejections could occur if handleExternalUrlContent throws an error outside of its try-catch blocks. While the function has internal error handling, any unexpected errors in the async chain (like in ensureBlockRefForFile, getFirstImageSrcForFile, or calcDiscourseNodeSize) that aren't caught will result in unhandled promise rejections.

editor.registerExternalContentHandler("url", async (externalContent) => {
  try {
    await handleExternalUrlContent({...});
  } catch (error) {
    console.error('Error handling external URL content:', error);
    new Notice('Failed to add content to canvas');
  }
});

Alternatively, if the handler must be synchronous, ensure handleExternalUrlContent has comprehensive error handling for all async operations.

Suggested change
editor.registerExternalContentHandler("url", (externalContent) => {
void handleExternalUrlContent({
editor,
url: externalContent.url,
point: externalContent.point,
plugin,
canvasFile: file,
defaultHandlerOpts: {
toasts: {
addToast: (t: Omit<TLUiToast, "id"> & { id?: string }) => {
new Notice(t.description ?? t.title ?? "Error");
return "";
},
removeToast: () => "",
clearToasts: () => {},
toasts: { get: () => [], update: () => {} },
},
msg: (key?: string) => key ?? "",
} as unknown as TLDefaultExternalContentHandlerOpts,
});
});
editor.registerExternalContentHandler("url", async (externalContent) => {
try {
await handleExternalUrlContent({
editor,
url: externalContent.url,
point: externalContent.point,
plugin,
canvasFile: file,
defaultHandlerOpts: {
toasts: {
addToast: (t: Omit<TLUiToast, "id"> & { id?: string }) => {
new Notice(t.description ?? t.title ?? "Error");
return "";
},
removeToast: () => "",
clearToasts: () => {},
toasts: { get: () => [], update: () => {} },
},
msg: (key?: string) => key ?? "",
} as unknown as TLDefaultExternalContentHandlerOpts,
});
} catch (error) {
console.error('Error handling external URL content:', error);
new Notice('Failed to add content to canvas');
}
});

Spotted by Graphite Agent

Fix in Graphite


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

Copy link
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 found 1 new potential issue.

View 8 additional findings in Devin Review.

Open in Devin Review

Comment on lines +270 to +290
editor.registerExternalContentHandler("url", (externalContent) => {
void handleExternalUrlContent({
editor,
url: externalContent.url,
point: externalContent.point,
plugin,
canvasFile: file,
defaultHandlerOpts: {
toasts: {
addToast: (t: Omit<TLUiToast, "id"> & { id?: string }) => {
new Notice(t.description ?? t.title ?? "Error");
return "";
},
removeToast: () => "",
clearToasts: () => {},
toasts: { get: () => [], update: () => {} },
},
msg: (key?: string) => key ?? "",
} as unknown as TLDefaultExternalContentHandlerOpts,
});
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 External content handler discards promise with void, breaking tldraw's async handler contract

The registerExternalContentHandler callback uses void handleExternalUrlContent(...) which discards the returned promise, causing the handler to return undefined synchronously instead of a Promise<void>.

Root Cause and Impact

Tldraw's registerExternalContentHandler expects the handler to return a Promise<void> so it can await the handler's completion. By using void (TldrawViewComponent.tsx:271), the promise from handleExternalUrlContent is discarded and the callback returns undefined synchronously.

When tldraw does await handler(content), it gets undefined, which resolves immediately. This means tldraw will consider the content handling "done" before the async work actually completes. Concretely:

  1. For obsidian:// URLs: ensureBlockRefForFile at externalContentHandlers.ts:170 is async and must complete before the shape can be created. Since tldraw isn't waiting, drag indicators and other UI may dismiss before the node appears on canvas.
  2. For non-obsidian URLs: The fallback call to defaultHandleExternalUrlContent at externalContentHandlers.ts:145 also runs fire-and-forget, so bookmark/embed creation may not complete properly.
  3. Error handling: Errors thrown after the first await inside handleExternalUrlContent become unhandled promise rejections instead of being caught by tldraw's error handling.

The fix is to return the promise instead of discarding it, e.g. return handleExternalUrlContent(...) instead of void handleExternalUrlContent(...).

Suggested change
editor.registerExternalContentHandler("url", (externalContent) => {
void handleExternalUrlContent({
editor,
url: externalContent.url,
point: externalContent.point,
plugin,
canvasFile: file,
defaultHandlerOpts: {
toasts: {
addToast: (t: Omit<TLUiToast, "id"> & { id?: string }) => {
new Notice(t.description ?? t.title ?? "Error");
return "";
},
removeToast: () => "",
clearToasts: () => {},
toasts: { get: () => [], update: () => {} },
},
msg: (key?: string) => key ?? "",
} as unknown as TLDefaultExternalContentHandlerOpts,
});
});
editor.registerExternalContentHandler("url", (externalContent) => {
return handleExternalUrlContent({
editor,
url: externalContent.url,
point: externalContent.point,
plugin,
canvasFile: file,
defaultHandlerOpts: {
toasts: {
addToast: (t: Omit<TLUiToast, "id"> & { id?: string }) => {
new Notice(t.description ?? t.title ?? "Error");
return "";
},
removeToast: () => "",
clearToasts: () => {},
toasts: { get: () => [], update: () => {} },
},
msg: (key?: string) => key ?? "",
} as unknown as TLDefaultExternalContentHandlerOpts,
});
});
Open in Devin Review

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

Copy link
Contributor

@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.

👍
Feel free to merge after dealing with #780 (comment)

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