Skip to content

fix(chat): let Enter send a message that starts with an unmatched @ or / - #464

Open
pjdoland wants to merge 1 commit into
plmbr:mainfrom
pjdoland:fix/463-unmatched-prefix-enter
Open

pjdoland wants to merge 1 commit into
plmbr:mainfrom
pjdoland:fix/463-unmatched-prefix-enter

Conversation

@pjdoland

Copy link
Copy Markdown
Collaborator

Summary

A chat message beginning with @ or / that matches no participant or slash command could not be sent from the keyboard. Enter threw TypeError: Cannot read properties of undefined (reading 'startsWith'), the message was never sent, and nothing appeared on screen: the draft stayed in the box and the transcript stayed empty. Clicking Send with the same text worked, which is what made the failure look arbitrary.

Ordinary prompts hit this, since any text opening with a name or a path qualifies: @Ada please review this, /usr/bin/python is missing, @here can someone look.

This is #463, found while exploring the chat input.

Solution

Typing @ or / opens the popover, and each later keystroke re-filters the suggestion list. The filter can empty that list, and nothing closed the popover when it did, so the Enter handler still tried to accept the highlighted suggestion: applyPrefixSuggestion(prefixSuggestions[0]) with an empty array passes undefined into a function whose first statement is prefix.startsWith('/mcp:').

The popover already rendered only when it had suggestions to show; the keyboard handlers disagreed with it. Both now read the same condition, so a popover with nothing to offer claims no keystroke and Enter falls through to sending the message. That covers three keys:

  • Enter threw, as above.
  • Tab made the identical call, so it threw the same way.
  • ArrowUp / ArrowDown computed (index ± 1 + length) % length, which is % 0 with an empty list, selecting NaN.

The condition and the filter move into a small module of their own. src/chat-sidebar.tsx is about 4,600 lines with no test coverage, and rendering it in jsdom would need a large JupyterLab mock, so extracting the two rules the regression is actually about is what makes it testable.

Testing

jlpm tsc --noEmit clean, jlpm lint:check clean, jlpm jest 540 passed across 46 suites, pytest 2104 passed.

New tests cover the filter (everything for an empty prompt, substring narrowing, nothing once the prompt is typed past every match, and no mutation of the caller's list) and the popover condition (false with nothing to suggest even while open, true while it has something, false when closed).

Verified in JupyterLab against a build of main for comparison:

before after
@Ada please review this + Enter TypeError, nothing sent, draft stuck sends; input clears; model replies; no console errors
Tab on the same text same TypeError leaves the prompt alone
/new + ArrowDown + Tab accepts /newPythonFile unchanged: accepts /newPythonFile
popover for /new lists both /new* commands unchanged

Risks and follow-ups

  • An exact match still takes two Enter presses: the first accepts the already-complete suggestion (adding a trailing space) and closes the popover, the second sends. That is conventional autocomplete behaviour, and treating "already fully typed" as nothing-to-accept would change accept semantics for MCP prompts, where accepting a fully typed /mcp:server:prompt legitimately opens the argument dialog. Left alone deliberately; worth its own decision.
  • Stopping a response mid-stream leaves the partial bubble with no indication it was cancelled (sometimes an empty bubble). Separate UX gap, noticed in the same pass, not touched here.

Closes #463

Typing `@` or `/` opens the suggestion popover, and every later keystroke
re-filters the list. The filter can empty it, and nothing closed the popover
when it did, so the Enter handler still tried to accept the highlighted
suggestion from an empty list: `applyPrefixSuggestion(undefined)`, which
throws on its first line. The message was never sent and nothing appeared on
screen, so any prompt opening with a name or a path (`@Ada please review
this`, `/usr/bin/python is missing`) could not be sent from the keyboard at
all. Clicking Send worked, which is what made it look so arbitrary.

The popover already rendered only when it had suggestions; the key handlers
disagreed. Both now read one condition, so a popover with nothing to offer
claims no keystroke. That covers Enter, Tab, and the arrow keys, whose
wraparound arithmetic divided by the same empty length and selected NaN.

The condition and the filter move into their own module: the sidebar is 4600
lines and has no tests, and these two rules are what the regression is about.

Closes plmbr#463
@pjdoland pjdoland added the bug Something isn't working label Sep 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(chat): Enter cannot send a message that starts with an unmatched @ or /

1 participant