authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-05 10:43:11-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-05 11:04:40-08:00
loga2682de4214cd277089fbf4a4c848d57a967f76d
tree2a53ceef2b5af09620bb211210a09c189b2bfb6b
parent325c0a3687d4dbd6ce536b059c7c2b7ad0c4964a

Use mention adjacency helper in composer onChange

- replace Lexical-node cursor adjacency check with text/cursor helper - prevent `@query` typing from being treated as mention-pill adjacent - add regression test for non-pill `@pac` input

2 files changed, 10 insertions(+), 24 deletions(-)

apps/web/src/components/ComposerPromptEditor.tsx+4-24
...@@ -558,29 +558,6 @@ function ComposerMentionSelectionNormalizePlugin() {...@@ -558,29 +558,6 @@ function ComposerMentionSelectionNormalizePlugin() {
558 return null;558 return null;
559}559}
560560
561function $isCursorAdjacentToMention(): boolean {
562 const selection = $getSelection();
563 if (!$isRangeSelection(selection) || !selection.isCollapsed()) return false;
564 const anchorNode = selection.anchor.getNode();
565
566 if (anchorNode instanceof ComposerMentionNode) return true;
567
568 if ($isElementNode(anchorNode)) {
569 const childIndex = selection.anchor.offset - 1;
570 if (childIndex >= 0) {
571 const child = anchorNode.getChildAtIndex(childIndex);
572 if (child instanceof ComposerMentionNode) return true;
573 }
574 }
575
576 if ($isTextNode(anchorNode) && selection.anchor.offset === 0) {
577 const prev = anchorNode.getPreviousSibling();
578 if (prev instanceof ComposerMentionNode) return true;
579 }
580
581 return false;
582}
583
584function ComposerMentionBackspacePlugin() {561function ComposerMentionBackspacePlugin() {
585 const [editor] = useLexicalComposerContext();562 const [editor] = useLexicalComposerContext();
586563
...@@ -756,7 +733,10 @@ function ComposerPromptEditorInner({...@@ -756,7 +733,10 @@ function ComposerPromptEditorInner({
756 value: nextValue,733 value: nextValue,
757 cursor: nextCursor,734 cursor: nextCursor,
758 };735 };
759 onChangeRef.current(nextValue, nextCursor, $isCursorAdjacentToMention());736 const cursorAdjacentToMention =
737 isCollapsedCursorAdjacentToMention(nextValue, nextCursor, "left") ||
738 isCollapsedCursorAdjacentToMention(nextValue, nextCursor, "right");
739 onChangeRef.current(nextValue, nextCursor, cursorAdjacentToMention);
760 });740 });
761 }, []);741 }, []);
762742
apps/web/src/composer-logic.test.ts+6
...@@ -85,6 +85,12 @@ describe("isCollapsedCursorAdjacentToMention", () => {...@@ -85,6 +85,12 @@ describe("isCollapsedCursorAdjacentToMention", () => {
85 expect(isCollapsedCursorAdjacentToMention("plain text", 6, "right")).toBe(false);85 expect(isCollapsedCursorAdjacentToMention("plain text", 6, "right")).toBe(false);
86 });86 });
8787
88 it("keeps @query typing non-adjacent while no mention pill exists", () => {
89 const text = "hello @pac";
90 expect(isCollapsedCursorAdjacentToMention(text, text.length, "left")).toBe(false);
91 expect(isCollapsedCursorAdjacentToMention(text, text.length, "right")).toBe(false);
92 });
93
88 it("detects left adjacency only when cursor is directly after a mention", () => {94 it("detects left adjacency only when cursor is directly after a mention", () => {
89 const text = "open @AGENTS.md next";95 const text = "open @AGENTS.md next";
90 const mentionStart = "open ".length;96 const mentionStart = "open ".length;