authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-02 13:54:02-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-02 14:16:08-08:00
log5e78386cdc805ee64ccc745454ce476475597f6f
tree5430e3351dbcc8595b1127520f2392ca2a0953e9
parentc378afeff852c06d7a558c4842ae9033b3775627

Improve composer menu highlighting and suppress triggers near mentions

Adds visual active-item highlighting to the command menu, suppresses trigger detection when the cursor is adjacent to a mention pill, and resets transient UI state on thread change. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

2 files changed, 59 insertions(+), 7 deletions(-)

apps/web/src/components/ChatView.tsx+33-4
......@@ -361,12 +361,16 @@ const VscodeEntryIcon = memo(function VscodeEntryIcon(props: {
361361const ComposerCommandMenuItem = memo(function ComposerCommandMenuItem(props: {
362362 item: ComposerCommandItem;
363363 resolvedTheme: "light" | "dark";
364 isActive: boolean;
364365 onSelect: (item: ComposerCommandItem) => void;
365366}) {
366367 return (
367368 <CommandItem
368369 value={props.item.id}
369 className="cursor-pointer select-none gap-2"
370 className={cn(
371 "cursor-pointer select-none gap-2",
372 props.isActive && "bg-accent text-accent-foreground",
373 )}
370374 onMouseDown={(event) => {
371375 event.preventDefault();
372376 }}
......@@ -400,6 +404,7 @@ const ComposerCommandMenu = memo(function ComposerCommandMenu(props: {
400404 resolvedTheme: "light" | "dark";
401405 isLoading: boolean;
402406 triggerKind: ComposerTriggerKind | null;
407 activeItemId: string | null;
403408 onHighlightedItemChange: (itemId: string | null) => void;
404409 onSelect: (item: ComposerCommandItem) => void;
405410}) {
......@@ -419,6 +424,7 @@ const ComposerCommandMenu = memo(function ComposerCommandMenu(props: {
419424 key={item.id}
420425 item={item}
421426 resolvedTheme={props.resolvedTheme}
427 isActive={props.activeItemId === item.id}
422428 onSelect={props.onSelect}
423429 />
424430 ))}
......@@ -1389,6 +1395,26 @@ export default function ChatView({ threadId }: ChatViewProps) {
13891395 scheduleStickToBottom();
13901396 }, [phase, scheduleStickToBottom, timelineEntries]);
13911397
1398 useEffect(() => {
1399 setExpandedWorkGroups({});
1400 }, [activeThread?.id]);
1401
1402 useEffect(() => {
1403 if (!composerMenuOpen) {
1404 setComposerHighlightedItemId(null);
1405 return;
1406 }
1407 setComposerHighlightedItemId((existing) =>
1408 existing && composerMenuItems.some((item) => item.id === existing)
1409 ? existing
1410 : (composerMenuItems[0]?.id ?? null),
1411 );
1412 }, [composerMenuItems, composerMenuOpen]);
1413
1414 useEffect(() => {
1415 setIsRevertingCheckpoint(false);
1416 }, [activeThread?.id]);
1417
13921418 useEffect(() => {
13931419 if (!activeThread?.id || terminalState.terminalOpen) return;
13941420 const frame = window.requestAnimationFrame(() => {
......@@ -2274,12 +2300,14 @@ export default function ChatView({ threadId }: ChatViewProps) {
22742300 workspaceEntriesQuery.isLoading ||
22752301 workspaceEntriesQuery.isFetching);
22762302
2277 const onPromptChange = useCallback((nextPrompt: string, nextCursor: number) => {
2303 const onPromptChange = useCallback((nextPrompt: string, nextCursor: number, cursorAdjacentToMention: boolean) => {
22782304 promptRef.current = nextPrompt;
22792305 setPrompt(nextPrompt);
22802306 setComposerCursor(nextCursor);
22812307 setComposerTrigger(
2282 detectComposerTrigger(nextPrompt, expandCollapsedComposerCursor(nextPrompt, nextCursor)),
2308 cursorAdjacentToMention
2309 ? null
2310 : detectComposerTrigger(nextPrompt, expandCollapsedComposerCursor(nextPrompt, nextCursor)),
22832311 );
22842312 }, [setPrompt]);
22852313
......@@ -2301,8 +2329,8 @@ export default function ChatView({ threadId }: ChatViewProps) {
23012329 const selectedItem = activeComposerMenuItemRef.current ?? currentItems[0];
23022330 if (selectedItem) {
23032331 onSelectComposerItem(selectedItem);
2332 return true;
23042333 }
2305 return true;
23062334 }
23072335 }
23082336
......@@ -2464,6 +2492,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
24642492 resolvedTheme={resolvedTheme}
24652493 isLoading={isComposerMenuLoading}
24662494 triggerKind={composerTriggerKind}
2495 activeItemId={activeComposerMenuItem?.id ?? null}
24672496 onHighlightedItemChange={onComposerMenuItemHighlighted}
24682497 onSelect={onSelectComposerItem}
24692498 />
apps/web/src/components/ComposerPromptEditor.tsx+26-3
......@@ -395,7 +395,7 @@ interface ComposerPromptEditorProps {
395395 disabled: boolean;
396396 placeholder: string;
397397 className?: string;
398 onChange: (nextValue: string, nextCursor: number) => void;
398 onChange: (nextValue: string, nextCursor: number, cursorAdjacentToMention: boolean) => void;
399399 onCommandKeyDown?: (
400400 key: "ArrowDown" | "ArrowUp" | "Enter" | "Tab",
401401 event: KeyboardEvent,
......@@ -540,6 +540,29 @@ function ComposerMentionSelectionNormalizePlugin() {
540540 return null;
541541}
542542
543function $isCursorAdjacentToMention(): boolean {
544 const selection = $getSelection();
545 if (!$isRangeSelection(selection) || !selection.isCollapsed()) return false;
546 const anchorNode = selection.anchor.getNode();
547
548 if (anchorNode instanceof ComposerMentionNode) return true;
549
550 if ($isElementNode(anchorNode)) {
551 const childIndex = selection.anchor.offset - 1;
552 if (childIndex >= 0) {
553 const child = anchorNode.getChildAtIndex(childIndex);
554 if (child instanceof ComposerMentionNode) return true;
555 }
556 }
557
558 if ($isTextNode(anchorNode) && selection.anchor.offset === 0) {
559 const prev = anchorNode.getPreviousSibling();
560 if (prev instanceof ComposerMentionNode) return true;
561 }
562
563 return false;
564}
565
543566function ComposerMentionBackspacePlugin() {
544567 const [editor] = useLexicalComposerContext();
545568
......@@ -662,7 +685,7 @@ function ComposerPromptEditorInner({
662685 value: snapshotRef.current.value,
663686 cursor: boundedCursor,
664687 };
665 onChangeRef.current(snapshotRef.current.value, boundedCursor);
688 onChangeRef.current(snapshotRef.current.value, boundedCursor, false);
666689 },
667690 [editor],
668691 );
......@@ -715,7 +738,7 @@ function ComposerPromptEditorInner({
715738 value: nextValue,
716739 cursor: nextCursor,
717740 };
718 onChangeRef.current(nextValue, nextCursor);
741 onChangeRef.current(nextValue, nextCursor, $isCursorAdjacentToMention());
719742 });
720743 }, []);
721744