From 5c68f7f5a8fd16cd280d2d9707dd3a88e2ca43e2 Mon Sep 17 00:00:00 2001 From: roboomp Date: Wed, 17 Jun 2026 12:14:13 +0000 Subject: [PATCH] fix(tui): surfaced skill commands in slash menu Prioritized /skill:* entries for the empty slash autocomplete query so user skills appear in the initial popup instead of below all built-ins. Added autocomplete regression coverage for empty slash query ordering. Fixes #2875 --- packages/tui/CHANGELOG.md | 4 ++++ packages/tui/src/autocomplete.ts | 4 +++- packages/tui/test/autocomplete.test.ts | 16 ++++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index 7f8a1ad0b..bbd13732a 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -6,6 +6,10 @@ - Coalesced byte-adjacent SGR sequences in emitted lines into a single `CSI … m`. The component tree styles each span as `text`, so adjacent spans emit runs of back-to-back SGR sequences (e.g. a `CSI 39 m` fg-reset immediately followed by the next span's `CSI 38;2;r;g;b m`); merging the run is behavior-preserving because SGR parameters apply left-to-right regardless of framing. On a real transcript this drops ~30-40% of all SGR sequences, cutting the per-frame byte volume and SGR-dispatch count a slow terminal engine (e.g. xterm.js/WebGL under a large viewport) must process. Each emitted sequence is capped at 16 parameter tokens so a long adjacent run is split across several valid CSIs instead of overflowing a terminal's parameter buffer (xterm.js caps at 32 and silently truncates, corrupting colors). A run is never extended past a parameter list that ends in an incomplete semicolon-form extended color (`38/48/58;2` missing a channel or `;5` missing the index), so a following code can't be absorbed as the missing component. Disable with `PI_NO_SGR_COALESCE=1`. +### Fixed + +- Fixed empty `/` autocomplete burying user skill commands below every built-in command, so installed skills appear in the initial slash popup ([#2875](https://github.com/can1357/oh-my-pi/issues/2875)). + ## [16.0.3] - 2026-06-16 ### Added diff --git a/packages/tui/src/autocomplete.ts b/packages/tui/src/autocomplete.ts index 48d72b34f..0621e570e 100644 --- a/packages/tui/src/autocomplete.ts +++ b/packages/tui/src/autocomplete.ts @@ -249,7 +249,9 @@ function buildSlashCommandCompletions(commands: CommandEntry[], lowerPrefix: str const fullDesc = hint ? (desc ? `${hint} — ${desc}` : hint) : desc; const candidates: Array = []; - const nameScore = scoreCommandTextMatch(lowerPrefix, name.toLowerCase()); + const isSkillCommand = name.startsWith("skill:"); + const nameScore = + lowerPrefix.length === 0 && isSkillCommand ? 950 : scoreCommandTextMatch(lowerPrefix, name.toLowerCase()); const lowerDesc = desc.toLowerCase(); const descScore = lowerDesc && fuzzyMatch(lowerPrefix, lowerDesc) ? fuzzyScore(lowerPrefix, lowerDesc) * 0.5 : 0; diff --git a/packages/tui/test/autocomplete.test.ts b/packages/tui/test/autocomplete.test.ts index 3dcbff8e2..81722fecc 100644 --- a/packages/tui/test/autocomplete.test.ts +++ b/packages/tui/test/autocomplete.test.ts @@ -61,6 +61,22 @@ describe("CombinedAutocompleteProvider", () => { }); }); + describe("slash commands", () => { + it("surfaces skill commands before built-ins for an empty slash query", async () => { + const provider = new CombinedAutocompleteProvider( + [ + { name: "settings", description: "Open settings" }, + { name: "model", description: "Switch model" }, + { name: "skill:reviewer", description: "Review code" }, + ], + "/tmp", + ); + + const result = await provider.getSuggestions(["/"], 0, 1); + + expect(result?.items.map(item => item.value).slice(0, 3)).toEqual(["skill:reviewer", "settings", "model"]); + }); + }); describe("applyCompletion", () => { it("replaces the live slash command prefix when rendered suggestions are stale", () => { const provider = new CombinedAutocompleteProvider([], "/tmp");