Did-you-mean command suggestions (#3027)#3029
Merged
Merged
Conversation
New players routinely type a subcommand as if it were the whole command (/teams or /team invite instead of /oneblock team) or mistype a subcommand (/island invit). Instead of "Unknown command" or a help dump, BentoBox now matches the input against every node of its command trees and replies with a clickable suggestion the player can also accept by typing "yes". - CommandMatcher: pure matching engine - label/alias matching at any tree depth, edit-distance and prefix heuristics, world-context ranking, permission filtering - SuggestionsManager: presentation and pending-intent tracking; single confident suggestion or a short clickable options list - DidYouMeanListener: UnknownCommandEvent interception (fires only when no plugin owns the command), chat "yes" acceptance, pending cleanup - CompositeCommand dispatch: unmatched subcommand args route through the suggestion engine before falling back to the old error - Config: general.did-you-mean.unknown-commands / .subcommands (default on) - Locales: new general.did-you-mean keys translated into all 22 languages; also fills the pre-existing gap commands.admin.team.setowner.specify-island everywhere Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jbkc3n7vnNbtMx3s9dbkbc
In-game testing showed the clickable suggestions rendering their closing tags as literal text, with the click event dead. Root cause: inline [run_command]/[hover] commands were re-encoded as MiniMessage tags wrapped around the message, but the message text contains a §r from the legacy round-trip (any closed color emits one). §r maps to <reset>, which closes the wrapping <click>/<hover> tags too - killing the events and orphaning the closers, which MiniMessage renders as literal text. Fix: extract the inline commands as data (Util.extractInlineCommands) and apply ClickEvent/HoverEvent programmatically to the parsed Component in User.parseToComponent. The events now cover the whole message regardless of internal resets. convertInlineCommandsToMiniMessage is kept (refactored over the new extraction) for API compatibility. Regression test asserts no literal tag text leaks and the click covers the message on both the single-suggestion and options-list paths. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jbkc3n7vnNbtMx3s9dbkbc
- User.parseToComponent (S2637): assign nullable record accessor to a local so the null-check narrows it for @nonnull parseMiniMessageOrLegacy - Util.extractInlineCommands (S6916 x2): replace the if statements inside the string-constant switch arms with first-wins ternary assignments - CommandMatcher.quality (S3358): extract the nested ternary into an allowedDistance helper - CommandMatcherTest (S5976): merge three identical matchCommandLine tests into one parameterized test Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G7UmQDCP5MGHEZqFiw44zH
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Implements the first slice of #3027: the did-you-mean layer, at both interception points.
What players see
/teams/team invite Floris/oneblock invit Floris/teamswith several game modes installed, standing at spawn/teamswith several game modes installed, standing in the OneBlock world/oneblock team— world context is decisiveAccepting is a click (
[run_command:]inline tag through the normal locale pipeline) or typingyes/yin chat within 30 seconds. Pending suggestions are dropped when the player runs any other command or quits.How it works
CommandMatcher(new, pure function, unit-tested standalone): matches typed tokens against the labels and aliases of every node in the command trees — because players type subcommands as if they were whole commands. Heuristics: exact > prefix > edit distance, scaled by length; ranking bonuses for the game mode world the player is standing in; permission-filtered; hidden/console-only/help commands never suggested.SuggestionsManager(new): presentation and pending-intent tracking. One clear winner → single suggestion + typed-yes intent; several plausible → up to 3 clickable options.DidYouMeanListener(new): listens toUnknownCommandEvent— which fires only when no plugin owns the command, so BentoBox never shadows another plugin — and replaces the vanilla "Unknown command" message. Chat listener consumesyes; command-preprocess and quit listeners clear pending intents.CompositeCommand.execute: when the dispatch walk stops with unconsumed args at a node with subcommands (previously a guaranteed help dump/error), the suggestion engine gets first refusal. Fully backward-compatible: if nothing plausible matches, the old path runs unchanged.Config
Two new toggles, default on (
config.yml→general.did-you-mean.unknown-commands/.subcommands).Locales
The three new
general.did-you-meankeys are translated into all 22 locale files. While syncing, the pre-existing gapcommands.admin.team.setowner.specify-island(missing from every non-English file) was filled too. All files passplaceholder_check.pyagainst en-US.Demo
DidYouMeanScenarioTestfollows the support transcript that motivated #3027 line by line (/teams→/team invite→/spawn):21 tests, including: the transcript cases, typed-yes acceptance, click-event assertions on the actual Components, world-context disambiguation, config toggles, permission filtering, and no-false-positive checks.
Closes #3027 (first slice: both interception points + matcher; telemetry hooks come with #3022).
🤖 Generated with Claude Code
https://claude.ai/code/session_01Jbkc3n7vnNbtMx3s9dbkbc