Persist the draft and re-sync popups at every programmatic-edit site

Review follow-up. The nickname tap wrote the field in code but never
wrote the draft, unlike onMessageTextChange, so switching conversations
restored the field without the tapped-in mention. The two suggestion
click handlers skipped the draft write the same way, and also set the
field without re-syncing the popups. All three sites now run the same
block a keyboard edit runs.

A new CommandProcessorTest case pins that command suggestions close
once the input stops prefixing a command name or alias, which is why
the two click sites had no user-visible popup repro: an input that can
match the mention popup has already closed the command popup.
This commit is contained in:
heyaim 2026-08-12 13:29:30 -05:00
parent 750abafaad
commit e8a9e819a8
2 changed files with 29 additions and 0 deletions

View File

@ -348,6 +348,9 @@ fun ChatScreen(viewModel: ChatViewModel) {
// Setting the field in code does not run onMessageTextChange, so
// the popups have to be re-synced with the new text here. The
// inserted mention ends with a space, which hides an open popup.
// The draft write is skipped the same way; without it, switching
// conversations restores the field without the tapped-in mention.
viewModel.setConversationDraft(selectedPrivatePeer, newText)
viewModel.updateCommandSuggestions(newText)
viewModel.updateMentionSuggestions(newText)
},
@ -439,6 +442,12 @@ fun ChatScreen(viewModel: ChatViewModel) {
text = commandText,
selection = TextRange(commandText.length)
)
// A code-driven edit skips onMessageTextChange: persist the
// draft and re-sync both popups, same as the other
// programmatic-edit sites.
viewModel.setConversationDraft(selectedPrivatePeer, commandText)
viewModel.updateCommandSuggestions(commandText)
viewModel.updateMentionSuggestions(commandText)
},
onMentionSuggestionClick = { mention: String ->
val mentionText = viewModel.selectMentionSuggestion(mention, messageText.text)
@ -446,6 +455,12 @@ fun ChatScreen(viewModel: ChatViewModel) {
text = mentionText,
selection = TextRange(mentionText.length)
)
// A code-driven edit skips onMessageTextChange: persist the
// draft and re-sync both popups, same as the other
// programmatic-edit sites.
viewModel.setConversationDraft(selectedPrivatePeer, mentionText)
viewModel.updateCommandSuggestions(mentionText)
viewModel.updateMentionSuggestions(mentionText)
},
selectedPrivatePeer = null,
currentChannel = currentChannel,

View File

@ -218,6 +218,20 @@ class CommandProcessorTest() {
assertTrue(chatState.getMentionSuggestionsValue().isEmpty())
}
@Test
fun `command suggestions close once the input grows past the command`() {
// The command popup only shows while the whole input is a prefix of a
// command name or alias, and none of them contains a space or an @. An
// input that can match the mention popup has already closed the command
// popup.
commandProcessor.updateCommandSuggestions("/m")
assertTrue(chatState.getShowCommandSuggestionsValue())
commandProcessor.updateCommandSuggestions("/msg @ali")
assertFalse(chatState.getShowCommandSuggestionsValue())
assertTrue(chatState.getCommandSuggestionsValue().isEmpty())
}
@Test
fun `a restored command draft reopens the command popup`() {
// Switching conversations restores the draft in code; re-syncing with the