Skip to content

feat(components): open the @ mention menu mid-sentence - #1153

Open
Gabyran wants to merge 5 commits into
LodyAI:mainfrom
Gabyran:feat/mention-trigger-mid-sentence
Open

Gabyran wants to merge 5 commits into
LodyAI:mainfrom
Gabyran:feat/mention-trigger-mid-sentence

Conversation

@Gabyran

@Gabyran Gabyran commented Sep 30, 2026 •

Copy link
Copy Markdown

Related issue

Closes #1152

Problem / pressure

@ opens the composer's mention menu only at the start of the input or after a space or newline. In scripts written without spaces (Chinese, Japanese), or when English or code is mixed into a sentence, 我想@张三 and fix this bug@alpha open nothing, so the user has to stop and insert a space before @.

Simply dropping the boundary guard has a cost: an email address being typed (gabi@example.com) would be matched against skills, files, and sessions by its domain. Detecting addresses must not swallow real mentions, though: 请@README.md and 我想@GPT-5.6-Code-Reviewer are mentions glued to CJK text, the very case this change is for.

Summary

  • @ no longer needs a word boundary before it in MentionInput's trigger detection. $, / and 、 keep the guard, so price$100, ${x} and src/foo stay plain text.
  • An email address being typed closes the menu from its first dot on and keeps it closed through the rest (gabi@example., gabi@example.com., gabi@example.com.cn), so it neither matches items nor reopens mid-address. It needs both an ASCII local part right against the @ and dotted domain labels ending in a letters-only tail. CJK before the @ (请@README.md), a standalone @README.md or open @package.json, a path (bug@src/a.b) and a name whose last label is not letters-only (bug@GPT-5.6-Code-Reviewer) keep the menu open. A partial user@example also keeps it open because there is no dot yet and it is still ambiguous.
  • Escape is now sticky for the trigger it dismissed. Before this, the next keystroke reopened the menu, so a user had no way to say "plain text here" when a guess was wrong. Typing on after a dismissed @ keeps the menu closed; text inserted before it shifts the record by the same rule mention ranges use (remapDismissedTrigger in mention-input-core.ts). Deleting the trigger, clearing the input, or typing a new trigger opens the menu again. This is the fallback for anything the email heuristic gets wrong.
  • mention-trigger.ts owns both shapes through isTriggerGluedToWord (the $ guard, replacing three inline lines) and looksLikeEmailAddress.
  • New mention-input-trigger.test.tsx (26 tests) and a remapDismissedTrigger case in mention-input-core.test.ts drives the real MentionInput and asserts the open/closed state.

Open questions for maintainers

  1. Where should this policy live? ui/mention/AGENTS.md describes the mention package as a product-neutral primitive, but this PR hard-codes trigger === '@' and an email heuristic in it. The existing trigger === '#' special case is precedent, though no production composer uses # (the composer configures @, $, /, 、). The alternative is a per-trigger predicate on MentionRoot, for example shouldOpen?: ({ trigger, value, triggerIndex, query }) => boolean, defaulting to today's word-boundary guard, with the composer supplying the @ rule and the email heuristic living under components/mentions/. That removes '@' and email knowledge from the primitive at the cost of one more prop and a stable-identity requirement for the callback. I kept this PR minimal so you can pick before more code is written.
  2. Is the residual ambiguity acceptable? Before the first dot, user@gmail has the menu open and Enter selects the highlighted item; it closes at gmail. and stays closed. bug@readme.md (English word, then a dotted name) is indistinguishable from an address, so it closes; typing a space first opens the file menu. bug@GPT-5.6-Code-Reviewer (ASCII word glued to a dotted role name) closes at GPT-5. and reopens at GPT-5.6, one flicker; with CJK before the @ it stays open throughout. git@github.com:org/repo.git is not a pure domain, so the menu re-opens after the colon.
  3. Does this need an Agent Note? feat: support Chinese command mention trigger #934 shipped a bilingual decision record for a trigger change. I did not add one, and did not touch AGENTS.md, since that needs maintainer approval.

Visual explanation

Trigger decision for each candidate trigger in onMentionUpdate (new nodes marked with *):

flowchart TD
    A[candidate trigger at index i] --> S{"dismissed by Escape?*"}
    S -- yes --> X[skip candidate]
    S -- no --> B{trigger is # or @?}
    B -- yes --> D
    B -- no --> C{"isTriggerGluedToWord(value, i)*"}
    C -- yes, e.g. price$100 --> X
    C -- no --> D{"trigger is @ and looksLikeEmailAddress(value, i, query)*<br/>ASCII local part + dotted labels, letters-only tail"}
    D -- yes, e.g. gabi@example. --> X
    D -- no --> E[existing checks: whitespace in query, interfering text]
    E --> F[open menu]
Loading
looksLikeEmailAddress(value, i, query)
  value[..i] ends with [A-Za-z0-9._%+-]        # ASCII local part against the @
  and query matches /^(?:[A-Za-z0-9-]+\.)+[A-Za-z]*$/   # dotted labels, letters-only (maybe empty) tail

我想@         -> open       (@ is exempt from the boundary guard)
user@example  -> open       (query has no dot yet, still ambiguous)
user@example.    -> closed  (ASCII local part + first dot; stays closed to .com.cn)
请@README.md  -> open       (CJK before @, no local part)
@README.md    -> open       (standalone)
price$        -> closed     ($ keeps the boundary guard)

Before / after

Before After
我想@ opens nothing; a space is required first. 我想@ opens the mention menu.
fix this bug@alpha opens nothing. Opens, and filters on alpha.
gabi@example.com opens nothing. Menu closes at the first dot of the domain and stays closed through .com.cn.
@README.md opens the file menu. Unchanged.
请@README.md opens nothing. Opens the mention menu.
我想@GPT-5.6-Code-Reviewer opens nothing. Opens the mention menu.
price$100 stays plain text. Unchanged.
user@gm, Escape, keep typing ail: the menu reopens on the next key. The menu stays closed for that @; a new @ or retyping it opens again.

Test plan

  • npx vitest run tests/mention-input-trigger.test.tsx tests/mention-input-core.test.ts: 26 + 13 passed. The cases added for review feedback fail against the previous commit and pass now, and the keystroke-by-keystroke gabi@example.com.cn test fails against the version that reopened the menu at example. and example.com..
  • npx vitest run tests/mention: 16 files, 231 tests passed. The two "stays closed after Escape" cases fail with the previous mention-input.tsx; the three "opens again" cases guard against over-dismissal and pass on both.
  • Composer-level suites (combined-mention-textarea-activation, path-mention-insert, session-mention-insert, issue-pr-mention-freshness, agent-role-mention-source): 49 passed.
  • packages/components: npm run typecheck (tsgo) clean.
  • oxlint --type-aware on src/ui/mention: 28 warnings, identical to main, no new ones.
  • oxfmt --check on the three changed files: clean, except the untouched findTriggerCandidates trailing comma that already fails on main; I left that line as is to keep the diff focused.
  • Full packages/components suite (npx vitest run): 541 files, 4584 tests passed.
  • Not run: root pnpm check (full CI), and no manual test with a native Chinese IME. IME composition is guarded by the existing isComposingRef early return, which this change does not touch.

Context handoff

Original user prompt

Show original prompt
我发现我们正在使用的这个 lody 软件,其中有一个功能就是 @。

使用了 @ 之后,如果它是在整个输入框的最开头,就可以调用到一些功能。但是如果它是在输入框的中间,比如我想在输入框的中间去 @ 一个 agent 角色,那么这个时候它必须要有一个空格,才能实现。

那我就在想,有没有可能我们把这个在句子中间直接 @ 可以调用一些命令的功能给它加上,然后去提一个 PR?

Shared conversation

Status: shared
Link: https://share.lody.ai/s/r173jnvz23xfcqx3nmsayjme5s8fdyby#access=v1.0e92410c5703b8ca354ff8aede6858bf42fc8cd288cdad70443d8384031fef7c
Additional link (review session of the same change): https://share.lody.ai/s/r17eggz7pjzaz9thh56bc31mg98fdk3n#access=v1.65e633f43f6f9378004ce3efa67aa0a6142906858b5d423204499745a3440aba

🤖 Generated with Claude Code

`@` opened the mention menu only at the start of the input or after a
space, so `我想@张三` or `fix this bug@alpha` opened nothing. `@` now opens
wherever it is typed. `$`, `/` and `、` keep the word-boundary guard so
code and paths stay plain text.

A glued `@` followed by a domain shape (`gabi@example.com`) is an email
address, not a mention attempt, so the menu closes instead of matching
the domain against skills, files, or sessions. A standalone
`@README.md` remains a file mention. `isTriggerGluedToWord` and
`looksLikeEmailAddress` in mention-trigger.ts own both shapes.

Refs LodyAI#1152

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@Gabyran

Gabyran commented Sep 30, 2026

Copy link
Copy Markdown
Author

这个改动虽然是个人的一个习惯之举,但我觉得从产品的角度来看,这是一个可以优化的小需求,就斗胆pr了,希望咱们团队负责产品优化的伙伴可以看看~

@Gabyran
Gabyran marked this pull request as ready for review September 30, 2026 08:05
@Lampese
Lampese self-requested a review September 30, 2026 08:23

@Lampese Lampese left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

感谢贡献!

发现了一个影响比较大的问题,如果您有空的话可以修一下,如果觉得比较麻烦的话我们下次功能调整的时候顺手帮您做了也可以~

triggerIndex: number,
search: string
): boolean {
return isTriggerGluedToWord(value, triggerIndex) && DOMAIN_QUERY_RE.test(search);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

好像这里的邮箱正则会拦截所有“紧贴前文且包含点号”的 @ 查询,而不只是邮箱
比如:

  • 请@README.md
  • 我想@GPT-5.6-Code-Reviewer

这些情况都会命中 DOMAIN_QUERY_RE,随后 onMentionUpdate 就直接 continue 了,菜单不会打开

Gabyran and others added 2 commits September 30, 2026 17:18
The email check closed the menu for any `@` glued to the preceding text
whose query had a dot, so `请@README.md` and `我想@GPT-5.6-Code-Reviewer`
never opened. Glued-after-CJK is the main case this feature exists for.

An address now needs both an ASCII local part right against the `@` and a
domain ending in an alphabetic TLD. CJK before the trigger, or a dotted
name whose last label is not letters-only, keeps the menu open.

Refs LodyAI#1152

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The address check only matched a finished domain, so typing
`gabi@example.com.cn` reopened the menu at `example.`, `example.c`,
`example.com.` and `example.com.c` before closing again.

Close from the first dot once the local part is ASCII: the domain regex
now accepts an empty or partial letters-only tail. CJK before the
trigger and names whose last label is not letters-only still keep the
menu open.

Refs LodyAI#1152

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@Gabyran
Gabyran marked this pull request as draft September 30, 2026 09:34
Gabyran and others added 2 commits October 1, 2026 08:35
Escape closed the menu, but the next keystroke reopened it, so a user
had no way to say "plain text here" when the email heuristic or any
other guess got it wrong.

Escape now records the trigger it dismissed. Typing on after it keeps
the menu closed; text inserted before it shifts the record by the same
rule mention ranges use (`remapDismissedTrigger`). Deleting the trigger,
clearing the input, or typing a new trigger opens the menu again.

Refs LodyAI#1152

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Gabyran
Gabyran marked this pull request as ready for review October 1, 2026 04:04
@Gabyran

Gabyran commented Oct 1, 2026

Copy link
Copy Markdown
Author

进行了一些改动,但由于其中逻辑确实复杂,也不确定自己是否解决了所有场景。附上更新后的对话链接:https://share.lody.ai/s/r173jnvz23xfcqx3nmsayjme5s8fdyby#access=v1.0e92410c5703b8ca354ff8aede6858bf42fc8cd288cdad70443d8384031fef7c 祝节日快乐!

@Gabyran
Gabyran requested a review from Lampese October 1, 2026 04:08
@Lampese

Lampese commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

进行了一些改动,但由于其中逻辑确实复杂,也不确定自己是否解决了所有场景。附上更新后的对话链接:https://share.lody.ai/s/r173jnvz23xfcqx3nmsayjme5s8fdyby#access=v1.0e92410c5703b8ca354ff8aede6858bf42fc8cd288cdad70443d8384031fef7c 祝节日快乐!

同乐!我觉得这个改动其实挺需要斟酌的,倒不是技术的问题,我也去调研了一下几个常见的产品,发到这里供参考:

  • 很多 Agent 产品都比较倾向于目前 Lody 的方案(或者有差异但是还是基本上倾向于 @ 与正文的内容有区分),即 @ 前面是输入开头、空格或换行,虽然我不觉得是很好的解决方案,但是确实已经有一部分用户有这个心智了
  • 这种问题一但要开始考虑那么就一定要把握度的问题,因为没有理论上 100% 完美的解决方案,优化一部分体验就一定要牺牲一部分体验,但是从能力上来说,我觉得更好的方案是用户用一种方法(比如打个空格)就可以 access 到 @ 的这个入口,而不是在某些情况下发现自己无法 access 到这个入口或者在不想 access 的时候受到打扰

因此这个问题我觉得可以先暂时挂起一下,我们也考虑一下有没有更好的解决方案

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] Open the @ mention menu mid-sentence without a preceding space

2 participants