Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -587,12 +587,9 @@ export function Prompt(props: PromptProps) {
dialog.replace(() => (
<DialogSkill
onSelect={(skill) => {
input.setText(`/${skill} `)
setStore("prompt", {
input: `/${skill} `,
parts: [],
})
input.gotoBufferEnd()
input.insertText(`/${skill} `)
setStore("prompt", "input", input.plainText)
Comment thread
HaleTom marked this conversation as resolved.
syncExtmarksWithPromptParts()
}}
/>
))
Expand Down
80 changes: 80 additions & 0 deletions packages/opencode/test/cli/tui/prompt-skill-select.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { describe, expect, test } from "bun:test"

// Regression test for the DialogSkill onSelect handler in
// packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx.
//
// Before the fix, onSelect called `input.setText(`/${skill} `)` which
// replaced the entire prompt content, wiping any text the user had already
// typed. After the fix, it calls `input.insertText(`/${skill} `)` which
// inserts at the cursor position, preserving existing text.
//
// `handleSkillSelect` below has the exact shape of the production handler after
// the fix.

type TextInput = {
plainText: string
insertText(text: string): void
}

type Store = {
prompt: { input: string; parts: unknown[] }
extmarkToPartIndex: Map<number, number>
}


function handleSkillSelect(input: TextInput, store: Store, syncExtmarks: () => void, skill: string) {
input.insertText(`/${skill} `)
store.prompt.input = input.plainText
syncExtmarks()
}

describe("Prompt skill picker onSelect", () => {
test("inserts skill name without wiping existing prompt text", () => {
let text = "some existing text"
const input: TextInput = {
get plainText() {
return text
},
insertText(insert: string) {
text = text + insert
},
Comment thread
HaleTom marked this conversation as resolved.
Outdated
}
const store: Store = {
prompt: { input: "some existing text", parts: [] },
extmarkToPartIndex: new Map(),
}
let synced = false

handleSkillSelect(input, store, () => { synced = true }, "test-skill")

expect(input.plainText).toBe("some existing text/test-skill ")
expect(store.prompt.input).toBe("some existing text/test-skill ")
expect(store.prompt.parts).toEqual([])
expect(synced).toBe(true)
})

test("inserts skill name into empty prompt", () => {
let text = ""
const input: TextInput = {
get plainText() {
return text
},
insertText(insert: string) {
text = text + insert
},
}
const store: Store = {
prompt: { input: "", parts: [] },
extmarkToPartIndex: new Map(),
}
let synced = false

handleSkillSelect(input, store, () => { synced = true }, "my-skill")

expect(input.plainText).toBe("/my-skill ")
expect(store.prompt.input).toBe("/my-skill ")
expect(synced).toBe(true)
})
})


Loading