fix(tui): separate question checkmark labels (#28558)

This commit is contained in:
Kit Langton
2026-05-20 21:35:59 -04:00
committed by GitHub
parent 12bbe84360
commit ddd6eb4496
5 changed files with 66 additions and 14 deletions

View File

@@ -416,7 +416,7 @@ export function RunQuestionBody(props: {
</text>
</box>
<Show when={!info()?.multiple}>
<text fg={props.theme.success}>{hit() ? "✓" : ""}</text>
<text fg={props.theme.success}>{hit() ? " ✓" : ""}</text>
</Show>
</box>
<box paddingLeft={3}>
@@ -466,7 +466,7 @@ export function RunQuestionBody(props: {
</text>
</box>
<Show when={!info()?.multiple}>
<text fg={props.theme.success}>{picked() ? "✓" : ""}</text>
<text fg={props.theme.success}>{picked() ? " ✓" : ""}</text>
</Show>
</box>
<Show

View File

@@ -375,7 +375,7 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
</text>
</box>
<Show when={!multi()}>
<text fg={theme.success}>{picked() ? "✓" : ""}</text>
<text fg={theme.success}>{picked() ? " ✓" : ""}</text>
</Show>
</box>
@@ -408,7 +408,7 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
</box>
<Show when={!multi()}>
<text fg={theme.success}>{customPicked() ? "✓" : ""}</text>
<text fg={theme.success}>{customPicked() ? " ✓" : ""}</text>
</Show>
</box>
<Show when={store.editing}>

View File

@@ -2,6 +2,7 @@
import { expect, test } from "bun:test"
import { testRender } from "@opentui/solid"
import { createSignal } from "solid-js"
import type { QuestionRequest } from "@opencode-ai/sdk/v2"
import {
RUN_COMMAND_PANEL_ROWS,
RUN_SUBAGENT_PANEL_ROWS,
@@ -24,6 +25,7 @@ import type {
RunProvider,
StreamCommit,
} from "@/cli/cmd/run/types"
import { RunQuestionBody } from "@/cli/cmd/run/footer.question"
function bindings(...keys: string[]) {
return keys.map((key) => ({ key }))
@@ -401,6 +403,53 @@ test("direct footer shows subagent indicator while prompt is running", async ()
}
})
test("direct question body separates single-select checkmark from label", async () => {
const request = {
id: "question-1",
sessionID: "session-1",
questions: [
{
question: "Which categorical concept is often described as a universal way to combine two objects?",
header: "Universal Product",
options: [
{ label: "Product", description: "A product comes with projections." },
{ label: "Equalizer", description: "An equalizer selects morphisms where arrows agree." },
],
},
],
} satisfies QuestionRequest
const replies: unknown[] = []
const app = await testRender(
() => (
<box width={100} height={12}>
<RunQuestionBody
request={request}
theme={RUN_THEME_FALLBACK.footer}
onReply={(input) => {
replies.push(input)
}}
onReject={() => {}}
/>
</box>
),
{
width: 100,
height: 12,
},
)
try {
app.mockInput.pressEnter()
await app.renderOnce()
expect(replies).toHaveLength(1)
expect(app.captureCharFrame()).toContain("Product ✓")
} finally {
app.renderer.destroy()
}
})
test("direct model panel renders current model selector", async () => {
const [providers] = createSignal<RunProvider[] | undefined>([provider()])
const [current] = createSignal<RunInput["model"]>({ providerID: "opencode", modelID: "gpt-5" })

View File

@@ -171,7 +171,7 @@ function globalSse(stream: GlobalEventStream) {
function wrapGlobalStream(stream: EventStream): GlobalEventStream {
return (async function* (): GlobalEventStream {
for await (const event of stream) {
yield globalEvent(event)
yield globalEvent(event as GlobalEvent["payload"])
}
return StreamClosed
})()
@@ -339,11 +339,11 @@ function child(id: string): SessionChild {
}
}
function globalEvent(payload: GlobalEvent["payload"]): GlobalEvent {
function globalEvent(payload: SdkEvent | GlobalEvent["payload"]): GlobalEvent {
return {
directory: "/tmp",
project: "project-1",
payload,
payload: payload as GlobalEvent["payload"],
}
}

View File

@@ -1,7 +1,7 @@
/** @jsxImportSource @opentui/solid */
import { describe, expect, test } from "bun:test"
import { testRender } from "@opentui/solid"
import type { Event, GlobalEvent } from "@opencode-ai/sdk/v2"
import type { GlobalEvent } from "@opencode-ai/sdk/v2"
import { onMount } from "solid-js"
import { ProjectProvider, useProject } from "../../../src/cli/cmd/tui/context/project"
import { SDKProvider } from "../../../src/cli/cmd/tui/context/sdk"
@@ -17,7 +17,10 @@ async function wait(fn: () => boolean, timeout = 2000) {
}
}
function event(payload: Event, input: { directory: string; project?: string; workspace?: string }): GlobalEvent {
function event(
payload: GlobalEvent["payload"],
input: { directory: string; project?: string; workspace?: string },
): GlobalEvent {
return {
directory: input.directory,
project: input.project,
@@ -26,7 +29,7 @@ function event(payload: Event, input: { directory: string; project?: string; wor
}
}
function vcs(branch: string): Event {
function vcs(branch: string): GlobalEvent["payload"] {
return {
id: `evt_vcs_${branch}`,
type: "vcs.branch.updated",
@@ -36,7 +39,7 @@ function vcs(branch: string): Event {
}
}
function update(version: string): Event {
function update(version: string): GlobalEvent["payload"] {
return {
id: `evt_update_${version}`,
type: "installation.update-available",
@@ -67,7 +70,7 @@ function createSource() {
async function mount() {
const source = createSource()
const seen: Event[] = []
const seen: GlobalEvent["payload"][] = []
const workspaces: Array<string | undefined> = []
const fetch = (async (input: RequestInfo | URL) => {
const url = new URL(input instanceof Request ? input.url : String(input))
@@ -102,7 +105,7 @@ async function mount() {
}
function Probe(props: {
seen: Event[]
seen: GlobalEvent["payload"][]
workspaces: Array<string | undefined>
onReady: (ctx: { project: ReturnType<typeof useProject> }) => void
}) {
@@ -111,7 +114,7 @@ function Probe(props: {
onMount(() => {
event.subscribe((evt, { workspace }) => {
props.seen.push(evt)
props.seen.push(evt as GlobalEvent["payload"])
props.workspaces.push(workspace)
})
props.onReady({ project })