From 0e8e162cd52fcae2d08d15539e1c583a512ecdb5 Mon Sep 17 00:00:00 2001 From: dotta Date: Sat, 21 Mar 2026 16:39:12 -0500 Subject: [PATCH] Fix mention pills by allowing custom URL schemes in Lexical LinkNode The previous fix (validateUrl on linkPlugin) only affected the link dialog, not the markdown-to-Lexical import path. Lexical's LinkNode.sanitizeUrl() converts agent:// and project:// URLs to about:blank because they aren't in its allowlist. Override the prototype method to preserve these schemes so mention chips render correctly. Co-Authored-By: Paperclip --- pnpm-lock.yaml | 9 ++++++--- ui/package.json | 3 ++- ui/src/components/MarkdownEditor.tsx | 11 +++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 56a8a46ad..cdc34c26d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,7 +22,7 @@ importers: version: 5.9.3 vitest: specifier: ^3.0.5 - version: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(jiti@2.6.1)(jsdom@28.1.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(tsx@4.21.0) + version: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2)(tsx@4.21.0) cli: dependencies: @@ -583,6 +583,9 @@ importers: '@dnd-kit/utilities': specifier: ^3.2.2 version: 3.2.2(react@19.2.4) + '@lexical/link': + specifier: 0.35.0 + version: 0.35.0 '@mdxeditor/editor': specifier: ^3.52.4 version: 3.52.4(@codemirror/language@6.12.1)(@lezer/highlight@1.2.3)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(yjs@13.6.29) @@ -685,7 +688,7 @@ importers: version: 6.4.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) vitest: specifier: ^3.0.5 - version: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(jiti@2.6.1)(jsdom@28.1.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(tsx@4.21.0) + version: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2)(tsx@4.21.0) packages: @@ -12164,7 +12167,7 @@ snapshots: - tsx - yaml - vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(jiti@2.6.1)(jsdom@28.1.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(tsx@4.21.0): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.30.2)(tsx@4.21.0): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 diff --git a/ui/package.json b/ui/package.json index 5ce155533..112fa86ef 100644 --- a/ui/package.json +++ b/ui/package.json @@ -13,14 +13,15 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", + "@lexical/link": "0.35.0", "@mdxeditor/editor": "^3.52.4", "@paperclipai/adapter-claude-local": "workspace:*", "@paperclipai/adapter-codex-local": "workspace:*", "@paperclipai/adapter-cursor-local": "workspace:*", "@paperclipai/adapter-gemini-local": "workspace:*", + "@paperclipai/adapter-openclaw-gateway": "workspace:*", "@paperclipai/adapter-opencode-local": "workspace:*", "@paperclipai/adapter-pi-local": "workspace:*", - "@paperclipai/adapter-openclaw-gateway": "workspace:*", "@paperclipai/adapter-utils": "workspace:*", "@paperclipai/shared": "workspace:*", "@radix-ui/react-slot": "^1.2.4", diff --git a/ui/src/components/MarkdownEditor.tsx b/ui/src/components/MarkdownEditor.tsx index afbd29fc9..84218a09f 100644 --- a/ui/src/components/MarkdownEditor.tsx +++ b/ui/src/components/MarkdownEditor.tsx @@ -26,11 +26,22 @@ import { thematicBreakPlugin, type RealmPlugin, } from "@mdxeditor/editor"; +import { LinkNode } from "@lexical/link"; import { buildAgentMentionHref, buildProjectMentionHref } from "@paperclipai/shared"; import { AgentIcon } from "./AgentIconPicker"; import { applyMentionChipDecoration, clearMentionChipDecoration, parseMentionChipHref } from "../lib/mention-chips"; import { cn } from "../lib/utils"; +/* ---- Allow custom mention URL schemes in Lexical's LinkNode ---- */ +// Lexical only allows http(s)/mailto/sms/tel by default, converting +// everything else to about:blank. We need agent:// and project:// +// to survive the markdown→Lexical import so mention chips render. +const _origSanitizeUrl = LinkNode.prototype.sanitizeUrl; +LinkNode.prototype.sanitizeUrl = function sanitizeUrl(url: string): string { + if (/^(agent|project):\/\//.test(url)) return url; + return _origSanitizeUrl.call(this, url); +}; + /* ---- Mention types ---- */ export interface MentionOption {