From 2180c2a59fbde47f689f2b40c832a452d16606e9 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Tue, 21 Jul 2026 13:52:53 +0700 Subject: [PATCH] fix(commands): remove examples from command discovery --- AGENTS.md | 8 ++-- README.md | 17 +++---- cmd/server/command_menu_test.go | 15 ++---- docs/deploy-coolify-selfhosted.md | 10 ++-- .../260721-1011-telegram-command-discovery.md | 47 ++++++++----------- internal/modules/coin/coin.go | 4 -- internal/modules/command_presentation.go | 9 ---- internal/modules/command_presentation_test.go | 17 ++----- internal/modules/gold/gold.go | 3 -- internal/modules/lol/lol.go | 1 - internal/modules/loldle/loldle.go | 1 - internal/modules/misc/misc.go | 3 -- internal/modules/misc/random_picker.go | 1 - internal/modules/misc/wheelofnames_command.go | 1 - internal/modules/module.go | 1 - internal/modules/stats/views.go | 1 - internal/modules/stock/stock.go | 7 --- internal/modules/util/help.go | 3 -- internal/modules/util/help_test.go | 13 ++--- internal/modules/validate.go | 19 -------- internal/modules/validate_test.go | 19 +------- internal/modules/wordle/wordle.go | 1 - 22 files changed, 47 insertions(+), 154 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 06881d5..b1fe65c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,7 +24,7 @@ Telegram command names are user-facing contracts. When adding, renaming, or deleting commands, update all related surfaces: - module command registration in `internal/modules//` -- command parameter and example metadata used by Telegram and `/help` +- command parameter metadata used by Telegram and `/help` - handler usage text and user-facing error text - tests for registration, handlers, and command menu behavior - README/docs when behavior changes are user-visible @@ -34,10 +34,8 @@ Include units or currencies when meaningful (for example, ``), use `[...]` for optional input, append `...` for remaining free text, and use parentheses to document structured input (for example, ``). Keep metadata, usage errors, examples, and tests exact. -Public commands with parameters must provide an explicit copyable example; -commands without parameters must omit it. In both Telegram's native menu and -`/help`, append `Eg: ` inline; `/help` must wrap only the invocation -in Telegram HTML `` formatting. +Telegram's native menu and `/help` show command syntax plus the summary without +example invocations. ## Stats Compatibility diff --git a/README.md b/README.md index a7f69f7..6bc7302 100644 --- a/README.md +++ b/README.md @@ -22,26 +22,23 @@ Disable modules with the `MODULES` environment variable. ## Command discovery Public command registrations share their description plus optional `Parameters` -and `Example` metadata between Telegram's native `/` menu and the bot's `/help` -response. Telegram renders the `/command` separately, and its native description -field supports only single-line plain text—no copyable code block. For -`/stock_buy`, the bot therefore sends this description: +metadata between Telegram's native `/` menu and the bot's `/help` response. +Telegram renders the `/command` separately, so `/stock_buy` uses this native +description: ```text - . Buy VN stock at market price. Eg: /stock_buy 100 TCB + . Buy VN stock at market price. ``` -`/help` combines the command syntax and summary, then appends `Eg:` and the -example on the same line. It wraps only the invocation in Telegram HTML -`` formatting so it is easy to copy. Public commands with parameters must -register an explicit example; commands without parameters omit it entirely. +`/help` combines the full command syntax and summary on one line. Neither +discovery surface includes example invocations. For future commands, use lowercase descriptive parameter names. Include units or currencies when they affect meaning (``, ``), use square brackets for optional input (`[date]`), append `...` when an argument accepts remaining text (`[target...]`), and describe structured input in parentheses (``, ``). Keep command -metadata, handler usage text, examples, tests, and this documentation aligned. +metadata, handler usage text, tests, and this documentation aligned. ### Stock dividend commands diff --git a/cmd/server/command_menu_test.go b/cmd/server/command_menu_test.go index 107570b..bf09d56 100644 --- a/cmd/server/command_menu_test.go +++ b/cmd/server/command_menu_test.go @@ -22,7 +22,7 @@ func TestBotCommandMenu_UsesLoadedPublicCommandsInModuleOrder(t *testing.T) { { Name: "beta", Commands: []modules.Command{ - {Name: "beta_public", Description: "Beta public", Parameters: "", Example: "/beta_public demo", Visibility: modules.VisibilityPublic}, + {Name: "beta_public", Description: "Beta public", Parameters: "", Visibility: modules.VisibilityPublic}, {Name: "beta_private", Description: "Beta private", Visibility: modules.VisibilityPrivate}, }, }, @@ -38,7 +38,7 @@ func TestBotCommandMenu_UsesLoadedPublicCommandsInModuleOrder(t *testing.T) { got := botCommandMenu(reg) want := []models.BotCommand{ - {Command: "beta_public", Description: ". Beta public. Eg: /beta_public demo"}, + {Command: "beta_public", Description: ". Beta public."}, {Command: "alpha_public", Description: "Alpha public."}, } if len(got) != len(want) { @@ -90,15 +90,10 @@ func TestCommandDiscovery_AllPublicCommandsHaveSafeMetadata(t *testing.T) { if got := command.Parameters; got != expectedParameters[command.Name] { t.Errorf("/%s parameters = %q, want %q", command.Name, got, expectedParameters[command.Name]) } - example := command.ExampleInvocation() - if command.Parameters == "" { - if example != "" || strings.Contains(command.TelegramMenuDescription(), "Eg:") { - t.Errorf("/%s without parameters has example %q", command.Name, example) - } - } else if !strings.HasPrefix(example, "/"+command.Name) { - t.Errorf("/%s example = %q", command.Name, example) - } description := command.TelegramMenuDescription() + if strings.Contains(description, "Eg:") { + t.Errorf("/%s native menu description contains an example: %q", command.Name, description) + } if strings.ContainsAny(description, "\r\n") { t.Errorf("/%s native menu description is multiline: %q", command.Name, description) } diff --git a/docs/deploy-coolify-selfhosted.md b/docs/deploy-coolify-selfhosted.md index 956a6ac..0dbcb52 100644 --- a/docs/deploy-coolify-selfhosted.md +++ b/docs/deploy-coolify-selfhosted.md @@ -137,12 +137,10 @@ Successful GIF replies include the result behind Telegram spoiler formatting. The bot registers its Telegram command menu from loaded public modules on every startup. The Go module registry is the single source of truth; no separate command-menu file or manual registration step is required. A command's -description plus optional `Parameters` and `Example` metadata feed both -surfaces. Telegram renders the command name separately and accepts only a -single-line plain-text description, so parameterized commands append -`Eg: ` inline. `/help` uses the same inline layout and wraps only -the invocation in Telegram HTML `` formatting. Commands without -parameters omit examples. +description plus optional `Parameters` metadata feed both surfaces. Telegram +renders the command name separately and accepts only a single-line plain-text +description. Both the native menu and `/help` show syntax plus the summary and +omit example invocations. ## Operations diff --git a/docs/journals/260721-1011-telegram-command-discovery.md b/docs/journals/260721-1011-telegram-command-discovery.md index caaac72..a0f841e 100644 --- a/docs/journals/260721-1011-telegram-command-discovery.md +++ b/docs/journals/260721-1011-telegram-command-discovery.md @@ -3,58 +3,51 @@ ## Context Telegram's native command menu and `/help` exposed only short descriptions, -leaving users to discover parameters and examples through failed invocations or -source documentation. +leaving users to discover parameters through failed invocations or source +documentation. ## What Changed -- Extended the shared command registration with `Parameters` and `Example` - metadata and presentation helpers used by both discovery surfaces. +- Extended the shared command registration with `Parameters` metadata and + presentation helpers used by both discovery surfaces. - Added metadata for all 40 public commands, including the exact stats grammar: `[users | user | cmd ]`. - Normalized placeholders to lowercase descriptive names, including meaningful units or currencies; `[...]` marks optional input, `...` remaining free text, and parentheses structured input. - Normalized `/wheelofnames` to `` across metadata, - usage text, and tests; its example remains - `/wheelofnames pizza, sushi, pho`. + usage text, and tests. - Finalized dividend placeholders as ` `, ` `, and ` ` for cash, share, and combined - commands. Examples and parsing remain unchanged. -- Native menu descriptions show only the summary for no-parameter commands. - Parameterized commands append an explicit example with the short `Eg:` label - on the same plain-text line. -- `/help` renders each invocation and summary together. Parameterized commands - append `Eg: invocation` on the same line, with only the copyable - invocation inside Telegram's HTML `` formatting. No-parameter commands - omit the label and example. Dynamic command fields are HTML-escaped. -- Registration validation rejects multiline metadata, examples for another - command, public commands that provide only one of parameters or example, and - public descriptions over Telegram's 256-character limit. + commands. Parsing remains unchanged. +- Native menu descriptions show parameters followed by the summary, while + `/help` renders the complete invocation followed by the summary. Neither + discovery surface includes example invocations. Dynamic fields are + HTML-escaped in `/help`. +- Registration validation rejects multiline metadata and public descriptions + over Telegram's 256-character limit. - Updated user and deployment documentation for the shared registry behavior. ## Reflection -Keeping syntax and examples beside each handler registration prevents the -native menu, `/help`, and implementation from drifting independently. The -native surface stays compact, while `/help` uses the richer layout Telegram -supports without sacrificing safe HTML rendering. +Keeping syntax beside each handler registration prevents the native menu, +`/help`, and implementation from drifting independently. Both discovery +surfaces stay compact without sacrificing safe HTML rendering in `/help`. ## Decisions - Existing command names, handlers, parsers, and persisted data remain unchanged; normalization is presentation-only. - No command was added, renamed, or deleted, so no stats migration is needed. -- Public commands with parameters require an explicit example; commands - without parameters omit it. +- Discovery surfaces intentionally omit example invocations; handler usage + errors may still include focused examples. - The complete `/help` output remains within Telegram's 4,096-character limit. ## Verification - Passed: command presentation, validation, menu, and `/help` tests for all 40 - public commands, including inline `` rendering, no-parameter omission, - and both parameter/example validation branches. + public commands. - Passed: `go test ./...`, including real MongoDB Testcontainers suites. - Passed: `go vet ./...` - Passed: `go build ./...` @@ -62,5 +55,5 @@ supports without sacrificing safe HTML rendering. ## Next Steps -- Require parameter and example metadata updates alongside future public - command contract changes. +- Require parameter metadata updates alongside future public command contract + changes. diff --git a/internal/modules/coin/coin.go b/internal/modules/coin/coin.go index f0f4e94..9224c4e 100644 --- a/internal/modules/coin/coin.go +++ b/internal/modules/coin/coin.go @@ -16,7 +16,6 @@ func New(deps modules.Deps) modules.Module { Visibility: modules.VisibilityPublic, Description: "Show current crypto price in USD", Parameters: "", - Example: "/coin_price BTC", Handler: s.handlePrice, }, { @@ -24,7 +23,6 @@ func New(deps modules.Deps) modules.Module { Visibility: modules.VisibilityPublic, Description: "Top up USD to your coin account", Parameters: "", - Example: "/coin_topup 1000", Handler: s.handleTopup, }, { @@ -32,7 +30,6 @@ func New(deps modules.Deps) modules.Module { Visibility: modules.VisibilityPublic, Description: "Spend a USD amount to buy coin", Parameters: " ", - Example: "/coin_buy BTC 10", Handler: s.handleBuy, }, { @@ -40,7 +37,6 @@ func New(deps modules.Deps) modules.Module { Visibility: modules.VisibilityPublic, Description: "Sell enough coin to receive a USD amount", Parameters: " ", - Example: "/coin_sell BTC 10", Handler: s.handleSell, }, { diff --git a/internal/modules/command_presentation.go b/internal/modules/command_presentation.go index e655ef7..1b1eca2 100644 --- a/internal/modules/command_presentation.go +++ b/internal/modules/command_presentation.go @@ -17,11 +17,6 @@ func (c Command) InvocationSentence() string { return withTerminalPunctuation(c.Invocation()) } -// ExampleInvocation returns the explicitly registered copyable example. -func (c Command) ExampleInvocation() string { - return strings.TrimSpace(c.Example) -} - // SummarySentence normalizes a command summary to a sentence without // duplicating terminal punctuation supplied by the registration. func (c Command) SummarySentence() string { @@ -38,10 +33,6 @@ func (c Command) TelegramMenuDescription() string { sb.WriteByte(' ') } sb.WriteString(c.SummarySentence()) - if example := c.ExampleInvocation(); example != "" { - sb.WriteString(" Eg: ") - sb.WriteString(example) - } return sb.String() } diff --git a/internal/modules/command_presentation_test.go b/internal/modules/command_presentation_test.go index 1b8274d..e285bc7 100644 --- a/internal/modules/command_presentation_test.go +++ b/internal/modules/command_presentation_test.go @@ -7,26 +7,22 @@ func TestCommandPresentation(t *testing.T) { name string command Command invocation string - example string menu string }{ { - name: "parameters and explicit example", + name: "parameters", command: Command{ Name: "stock_buy", Parameters: " ", Description: "Buy VN stock at market price", - Example: "/stock_buy 100 TCB", }, invocation: "/stock_buy ", - example: "/stock_buy 100 TCB", - menu: " . Buy VN stock at market price. Eg: /stock_buy 100 TCB", + menu: " . Buy VN stock at market price.", }, { - name: "no parameters omits example", + name: "no parameters", command: Command{Name: "ping", Description: "Health check!"}, invocation: "/ping", - example: "", menu: "Health check!", }, { @@ -35,11 +31,9 @@ func TestCommandPresentation(t *testing.T) { Name: "random", Parameters: "", Description: "Pick one option", - Example: "/random pizza, sushi", }, invocation: "/random ", - example: "/random pizza, sushi", - menu: ". Pick one option. Eg: /random pizza, sushi", + menu: ". Pick one option.", }, } @@ -48,9 +42,6 @@ func TestCommandPresentation(t *testing.T) { if got := tc.command.Invocation(); got != tc.invocation { t.Errorf("Invocation() = %q, want %q", got, tc.invocation) } - if got := tc.command.ExampleInvocation(); got != tc.example { - t.Errorf("ExampleInvocation() = %q, want %q", got, tc.example) - } if got := tc.command.TelegramMenuDescription(); got != tc.menu { t.Errorf("TelegramMenuDescription() = %q, want %q", got, tc.menu) } diff --git a/internal/modules/gold/gold.go b/internal/modules/gold/gold.go index 93ee75c..9e0c5f3 100644 --- a/internal/modules/gold/gold.go +++ b/internal/modules/gold/gold.go @@ -21,7 +21,6 @@ func New(deps modules.Deps) modules.Module { Visibility: modules.VisibilityPublic, Description: "Top up VND to your gold account", Parameters: "", - Example: "/gold_topup 5000000", Handler: s.handleTopup, }, { @@ -29,7 +28,6 @@ func New(deps modules.Deps) modules.Module { Visibility: modules.VisibilityPublic, Description: "Buy gold at SJC sell price", Parameters: "", - Example: "/gold_buy 1", Handler: s.handleBuy, }, { @@ -37,7 +35,6 @@ func New(deps modules.Deps) modules.Module { Visibility: modules.VisibilityPublic, Description: "Sell gold at SJC buy price", Parameters: "", - Example: "/gold_sell 0.5", Handler: s.handleSell, }, { diff --git a/internal/modules/lol/lol.go b/internal/modules/lol/lol.go index 04009b8..f41d7a6 100644 --- a/internal/modules/lol/lol.go +++ b/internal/modules/lol/lol.go @@ -27,7 +27,6 @@ func New(deps modules.Deps) modules.Module { Visibility: modules.VisibilityPublic, Description: "LoL matches for a date (dd, dd-mm, dd/mm, ddmm, or full date; default today)", Parameters: "[date]", - Example: "/lol 21-07-2026", Handler: s.handleSchedule, }, { diff --git a/internal/modules/loldle/loldle.go b/internal/modules/loldle/loldle.go index e5559f2..d646f0f 100644 --- a/internal/modules/loldle/loldle.go +++ b/internal/modules/loldle/loldle.go @@ -21,7 +21,6 @@ func New(deps modules.Deps) modules.Module { Visibility: modules.VisibilityPublic, Description: "Classic loldle — guess the current champion", Parameters: "[champion]", - Example: "/loldle Ahri", Handler: s.handleLoldle, }, { diff --git a/internal/modules/misc/misc.go b/internal/modules/misc/misc.go index 79becdd..08fa118 100644 --- a/internal/modules/misc/misc.go +++ b/internal/modules/misc/misc.go @@ -129,17 +129,14 @@ func senderMention(u *models.User) string { func disclaimerCommand(name, description, defaultTarget string, allowCustomTarget bool) modules.Command { parameters := "" - example := "" if allowCustomTarget { parameters = "[target...]" - example = "/" + name + " FBI" } return modules.Command{ Name: name, Visibility: modules.VisibilityPublic, Description: description, Parameters: parameters, - Example: example, Handler: func(ctx context.Context, b *bot.Bot, update *models.Update) error { if update.Message == nil { return nil diff --git a/internal/modules/misc/random_picker.go b/internal/modules/misc/random_picker.go index e59ed57..65681b5 100644 --- a/internal/modules/misc/random_picker.go +++ b/internal/modules/misc/random_picker.go @@ -31,7 +31,6 @@ func randomCommand() modules.Command { Visibility: modules.VisibilityPublic, Description: "Pick one random comma-separated option", Parameters: "", - Example: "/random pizza, sushi, pho", Handler: func(ctx context.Context, b *bot.Bot, update *models.Update) error { if update.Message == nil { return nil diff --git a/internal/modules/misc/wheelofnames_command.go b/internal/modules/misc/wheelofnames_command.go index 37f68f8..c15e610 100644 --- a/internal/modules/misc/wheelofnames_command.go +++ b/internal/modules/misc/wheelofnames_command.go @@ -25,7 +25,6 @@ func wheelOfNamesCommand() modules.Command { Visibility: modules.VisibilityPublic, Description: "Pick one comma-separated option with wheel GIF when configured", Parameters: "", - Example: "/wheelofnames pizza, sushi, pho", Handler: func(ctx context.Context, b *bot.Bot, update *models.Update) error { if update.Message == nil { return nil diff --git a/internal/modules/module.go b/internal/modules/module.go index 58692cf..e335887 100644 --- a/internal/modules/module.go +++ b/internal/modules/module.go @@ -40,7 +40,6 @@ type Command struct { Visibility Visibility // public/protected/private Description string // concise summary shown in command discovery (required, non-empty) Parameters string // optional syntax after the command, e.g. " " - Example string // full invocation required with public Parameters; omitted without them Handler CommandHandler // required } diff --git a/internal/modules/stats/views.go b/internal/modules/stats/views.go index 840eacb..26a1446 100644 --- a/internal/modules/stats/views.go +++ b/internal/modules/stats/views.go @@ -33,7 +33,6 @@ func statsCommand(c *counter) modules.Command { Visibility: modules.VisibilityPublic, Description: "Show command usage statistics", Parameters: "[users | user | cmd ]", - Example: "/stats user alice", Handler: func(ctx context.Context, b *bot.Bot, update *models.Update) error { if update.Message == nil { return nil diff --git a/internal/modules/stock/stock.go b/internal/modules/stock/stock.go index c010019..264b488 100644 --- a/internal/modules/stock/stock.go +++ b/internal/modules/stock/stock.go @@ -15,7 +15,6 @@ func New(deps modules.Deps) modules.Module { Visibility: modules.VisibilityPublic, Description: "Show current VN stock price", Parameters: "", - Example: "/stock_price TCB", Handler: s.handlePrice, }, { @@ -23,7 +22,6 @@ func New(deps modules.Deps) modules.Module { Visibility: modules.VisibilityPublic, Description: "Top up VND to your stock account", Parameters: "", - Example: "/stock_topup 5000000", Handler: s.handleTopup, }, { @@ -31,7 +29,6 @@ func New(deps modules.Deps) modules.Module { Visibility: modules.VisibilityPublic, Description: "Buy VN stock at market price", Parameters: " ", - Example: "/stock_buy 100 TCB", Handler: s.handleBuy, }, { @@ -39,7 +36,6 @@ func New(deps modules.Deps) modules.Module { Visibility: modules.VisibilityPublic, Description: "Sell VN stock back to VND", Parameters: " ", - Example: "/stock_sell 100 TCB", Handler: s.handleSell, }, { @@ -47,7 +43,6 @@ func New(deps modules.Deps) modules.Module { Visibility: modules.VisibilityPublic, Description: "Record cash dividend", Parameters: " ", - Example: "/stock_cash_dividend 1500 TCB", Handler: s.handleCashDividend, }, { @@ -55,7 +50,6 @@ func New(deps modules.Deps) modules.Module { Visibility: modules.VisibilityPublic, Description: "Record share dividend", Parameters: " ", - Example: "/stock_share_dividend 100:10 TCB", Handler: s.handleShareDividend, }, { @@ -63,7 +57,6 @@ func New(deps modules.Deps) modules.Module { Visibility: modules.VisibilityPublic, Description: "Record cash and share dividend", Parameters: " ", - Example: "/stock_dividend 1500 100:10 TCB", Handler: s.handleDividend, }, { diff --git a/internal/modules/util/help.go b/internal/modules/util/help.go index d30e9ed..9870110 100644 --- a/internal/modules/util/help.go +++ b/internal/modules/util/help.go @@ -50,9 +50,6 @@ func RenderHelp(reg *modules.Registry) string { fmt.Fprintf(&sb, "\n%s %s", html.EscapeString(command.InvocationSentence()), html.EscapeString(command.SummarySentence())) - if example := command.ExampleInvocation(); example != "" { - fmt.Fprintf(&sb, " Eg: %s", html.EscapeString(example)) - } } sections = append(sections, sb.String()) } diff --git a/internal/modules/util/help_test.go b/internal/modules/util/help_test.go index 04666ef..5bcd99b 100644 --- a/internal/modules/util/help_test.go +++ b/internal/modules/util/help_test.go @@ -74,13 +74,12 @@ func TestRenderHelp_GroupsByModuleAndSkipsNonPublic(t *testing.T) { } } -func TestRenderHelp_ShowsParametersAndCopyableExample(t *testing.T) { +func TestRenderHelp_ShowsParametersWithoutExample(t *testing.T) { command := modules.Command{ Name: "buy", Visibility: modules.VisibilityPublic, Description: "Buy & hold", Parameters: " ", - Example: "/buy 100 TCB & hold", Handler: helpTestNoop, } reg, err := modules.Build( @@ -97,14 +96,8 @@ func TestRenderHelp_ShowsParametersAndCopyableExample(t *testing.T) { if !strings.Contains(out, "/buy <quantity> <ticker>. Buy & hold.") { t.Fatalf("help missing formatted invocation and summary:\n%s", out) } - if !strings.Contains(out, "/buy 100 TCB & hold") { - t.Fatalf("help missing copyable example:\n%s", out) - } - if !strings.Contains(out, "Buy & hold. Eg: /buy 100 TCB & hold") { - t.Fatalf("help missing short example label:\n%s", out) - } - if strings.Contains(out, "
") {
-		t.Fatalf("help example should use inline code, not a pre block:\n%s", out)
+	if strings.Contains(out, "Eg:") || strings.Contains(out, "") || strings.Contains(out, "
") {
+		t.Fatalf("help should not render examples:\n%s", out)
 	}
 }
 
diff --git a/internal/modules/validate.go b/internal/modules/validate.go
index 413d12f..ac34444 100644
--- a/internal/modules/validate.go
+++ b/internal/modules/validate.go
@@ -29,25 +29,6 @@ func validateCommand(c Command) error {
 	if strings.ContainsAny(c.Parameters, "\r\n") {
 		return fmt.Errorf("command %q: parameters must be single-line", c.Name)
 	}
-	if strings.ContainsAny(c.Example, "\r\n") {
-		return fmt.Errorf("command %q: example must be single-line", c.Name)
-	}
-	parameters := strings.TrimSpace(c.Parameters)
-	example := strings.TrimSpace(c.Example)
-	if c.Visibility == VisibilityPublic {
-		if parameters == "" && example != "" {
-			return fmt.Errorf("command %q: example requires parameters", c.Name)
-		}
-		if parameters != "" && example == "" {
-			return fmt.Errorf("command %q: example is required when parameters are present", c.Name)
-		}
-	}
-	if example != "" {
-		prefix := "/" + c.Name
-		if example != prefix && !strings.HasPrefix(example, prefix+" ") {
-			return fmt.Errorf("command %q: example must invoke %s", c.Name, prefix)
-		}
-	}
 	if c.Visibility == VisibilityPublic && utf8.RuneCountInString(c.TelegramMenuDescription()) > telegramCommandDescriptionMaxRunes {
 		return fmt.Errorf("command %q: Telegram menu description exceeds %d characters", c.Name, telegramCommandDescriptionMaxRunes)
 	}
diff --git a/internal/modules/validate_test.go b/internal/modules/validate_test.go
index ba5d525..9404597 100644
--- a/internal/modules/validate_test.go
+++ b/internal/modules/validate_test.go
@@ -47,22 +47,6 @@ func TestValidateCommand_RejectsInvalidPresentationMetadata(t *testing.T) {
 			name:    "multiline parameters",
 			command: Command{Name: "ok", Visibility: VisibilityPublic, Description: "d", Parameters: "\n", Handler: okHandler},
 		},
-		{
-			name:    "multiline example",
-			command: Command{Name: "ok", Visibility: VisibilityPublic, Description: "d", Example: "/ok one\n/ok two", Handler: okHandler},
-		},
-		{
-			name:    "example invokes another command",
-			command: Command{Name: "ok", Visibility: VisibilityPublic, Description: "d", Example: "/other", Handler: okHandler},
-		},
-		{
-			name:    "example without parameters",
-			command: Command{Name: "ok", Visibility: VisibilityPublic, Description: "d", Example: "/ok", Handler: okHandler},
-		},
-		{
-			name:    "parameters without example",
-			command: Command{Name: "ok", Visibility: VisibilityPublic, Description: "d", Parameters: "", Handler: okHandler},
-		},
 		{
 			name:    "native description too long",
 			command: Command{Name: "ok", Visibility: VisibilityPublic, Description: strings.Repeat("x", 257), Handler: okHandler},
@@ -86,13 +70,12 @@ func TestValidateCommand_AcceptsLegalNames(t *testing.T) {
 	}
 }
 
-func TestValidateCommand_AcceptsMatchedParametersAndExample(t *testing.T) {
+func TestValidateCommand_AcceptsParameters(t *testing.T) {
 	command := Command{
 		Name:        "ok",
 		Visibility:  VisibilityPublic,
 		Description: "Do it",
 		Parameters:  "",
-		Example:     "/ok demo",
 		Handler:     okHandler,
 	}
 	if err := validateCommand(command); err != nil {
diff --git a/internal/modules/wordle/wordle.go b/internal/modules/wordle/wordle.go
index 348fe45..9574b8f 100644
--- a/internal/modules/wordle/wordle.go
+++ b/internal/modules/wordle/wordle.go
@@ -23,7 +23,6 @@ func New(deps modules.Deps) modules.Module {
 				Visibility:  modules.VisibilityPublic,
 				Description: "Classic wordle — guess the 5-letter word",
 				Parameters:  "[word]",
-				Example:     "/wordle apple",
 				Handler:     s.handleWordle,
 			},
 			{