+ {/* Drag handle dots */}
+ {showHandles &&
+ handlePositions.map((pos, i) => (
+
handlePointerDown(e, pos.anchor)}
+ style={{
+ position: "absolute",
+ left: `${pos.left}px`,
+ top: `${pos.top}px`,
+ transform: "translate(-50%, -50%)",
+ width: `${HANDLE_HIT_AREA * 2}px`,
+ height: `${HANDLE_HIT_AREA * 2}px`,
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ cursor: "crosshair",
+ pointerEvents: "all",
+ zIndex: 20,
+ }}
+ >
+
+
+ ))}
+
+ {/* Relation type dropdown — new arrow */}
+ {pendingArrowId && (
+
+ )}
+
+ {/* Relation type dropdown — edit existing arrow */}
+ {editingArrowId && !pendingArrowId && (
+
+ )}
+
+ );
+};
diff --git a/apps/obsidian/src/components/canvas/overlays/RelationTypeDropdown.tsx b/apps/obsidian/src/components/canvas/overlays/RelationTypeDropdown.tsx
new file mode 100644
index 000000000..e319d3e6c
--- /dev/null
+++ b/apps/obsidian/src/components/canvas/overlays/RelationTypeDropdown.tsx
@@ -0,0 +1,230 @@
+import { useCallback, useEffect, useMemo, useRef } from "react";
+import { TLShapeId, useEditor, useValue } from "tldraw";
+import DiscourseGraphPlugin from "~/index";
+import { DiscourseRelationShape } from "~/components/canvas/shapes/DiscourseRelationShape";
+import {
+ getArrowBindings,
+ getArrowInfo,
+} from "~/components/canvas/utils/relationUtils";
+import { COLOR_PALETTE } from "~/utils/tldrawColors";
+
+type RelationTypeDropdownProps = {
+ arrowId: TLShapeId;
+ plugin: DiscourseGraphPlugin;
+ onSelect: (relationTypeId: string) => void;
+ onDismiss: () => void;
+};
+
+export const RelationTypeDropdown = ({
+ arrowId,
+ plugin,
+ onSelect,
+ onDismiss,
+}: RelationTypeDropdownProps) => {
+ const editor = useEditor();
+ const dropdownRef = useRef