fix(tui): use unicode ellipses in interface text (#45143)

This commit is contained in:
David Hill
2026-08-25 21:26:15 -06:00
committed by GitHub
parent 9361117504
commit 474c3588c1
12 changed files with 31 additions and 31 deletions
+2 -2
View File
@@ -614,7 +614,7 @@ function App(props: { pair?: DialogPairCredentials }) {
return
}
renderer.setTerminalTitle(`OC | ${title.length > 40 ? title.slice(0, 37) + "..." : title}`)
renderer.setTerminalTitle(`OC | ${title.length > 40 ? title.slice(0, 37) + "" : title}`)
return
}
@@ -971,7 +971,7 @@ function App(props: { pair?: DialogPairCredentials }) {
const restart = client.restart
if (!restart) return
dialog.clear()
toast.show({ variant: "info", message: "Restarting service...", duration: 30000 })
toast.show({ variant: "info", message: "Restarting service", duration: 30000 })
// restart resolves once the replacement service is healthy; the
// event stream reattaches through the reconnect loop.
await restart()
+1 -1
View File
@@ -360,7 +360,7 @@ export function DevToolsBar() {
<PanelBox>
<PanelTitle>Tools</PanelTitle>
<Action onClick={() => void dump()} disabled={dumping()} hoverBackground>
{dumping() ? "Writing debug snapshot..." : "Write debug snapshot"}
{dumping() ? "Writing debug snapshot" : "Write debug snapshot"}
</Action>
<Show when={dumpPath()}>
{(file) => (
@@ -352,7 +352,7 @@ function CommandStarting(props: {
if (!handedOff) closed = true
})
return <CommandView title={props.method.label} output="" message="Starting command..." />
return <CommandView title={props.method.label} output="" message="Starting command" />
}
function CommandPending(props: {
@@ -412,7 +412,7 @@ function CommandPending(props: {
})
})
return <CommandView title={props.title} output={output()} message="Waiting for command to finish..." />
return <CommandView title={props.title} output={output()} message="Waiting for command to finish" />
}
function CommandView(props: { title: string; output: string; message: string }) {
@@ -541,7 +541,7 @@ function OAuthStarting(props: {
})
})
return <OAuthView title={props.method.label} message="Starting authorization..." />
return <OAuthView title={props.method.label} message="Starting authorization" />
}
function OAuthAuto(props: {
@@ -629,7 +629,7 @@ function OAuthAuto(props: {
title={props.title}
url={props.attempt.url}
instructions={props.attempt.instructions}
message="Waiting for authorization..."
message="Waiting for authorization"
copy
open
/>
@@ -953,7 +953,7 @@ async function externalAnswer(
if (choice === true) return true
const result = await new Promise<boolean | typeof CANCELLED>((resolve) => {
dialog.replace(
() => <OAuthView title={formLabel(field) || title} message="Opening link..." />,
() => <OAuthView title={formLabel(field) || title} message="Opening link" />,
() => resolve(CANCELLED),
)
void open(field.url).then(
@@ -237,7 +237,7 @@ function buildIssueURL(message: string, stack: string, version: string) {
// clipped trace is obvious. searchParams.set handles encoding without throwing,
// so measuring url.toString() is both correct and safe on any input.
const MAX_URL_LENGTH = 6000
const marker = "\n... (truncated)"
const marker = "\n (truncated)"
const head = `The OpenCode TUI crashed with an unexpected error.\n\n**Error:** ${message}\n\n**Stack trace:**\n`
const setBody = (body: string) => url.searchParams.set("description", head + "```\n" + body + "\n```")
+2 -2
View File
@@ -1641,10 +1641,10 @@ export function Prompt(props: PromptProps) {
const value = (() => {
if (store.mode === "shell") {
if (!shell().length) return undefined
return `Run a command... "${shell()[store.placeholder % shell().length]}"`
return `Run a command "${shell()[store.placeholder % shell().length]}"`
}
if (!list().length) return undefined
return `Ask anything... "${list()[store.placeholder % list().length]}"`
return `Ask anything "${list()[store.placeholder % list().length]}"`
})()
if (!value) return undefined
const width = dimensions().width < 44 ? dimensions().width - 5 : Math.min(75, dimensions().width - 4) - 5
+1 -1
View File
@@ -28,7 +28,7 @@ export function Reconnecting(props: { managed?: boolean }) {
paddingRight={2}
gap={1}
>
<Spinner color={theme.text.default}>{props.managed ? "Restarting service..." : "Connection lost..."}</Spinner>
<Spinner color={theme.text.default}>{props.managed ? "Restarting service" : "Connection lost"}</Spinner>
<text fg={theme.text.subdued}>
{props.managed
? "Your session will resume automatically."
@@ -5,7 +5,7 @@ import { Spinner } from "./spinner"
export function StartupLoading(props: { ready: () => boolean }) {
const theme = useTheme("elevated")
const [show, setShow] = createSignal(false)
const text = createMemo(() => (props.ready() ? "Finishing startup..." : "Loading plugins..."))
const text = createMemo(() => (props.ready() ? "Finishing startup" : "Loading plugins"))
let wait: NodeJS.Timeout | undefined
let hold: NodeJS.Timeout | undefined
let stamp = 0
@@ -75,7 +75,7 @@ export function DialogFork(props: { sessionID: string; messageID?: string; onMov
when={!pending()}
fallback={
<box paddingLeft={2} paddingRight={2} paddingBottom={1}>
<Spinner>Forking session...</Spinner>
<Spinner>Forking session</Spinner>
</box>
}
>
+12 -12
View File
@@ -3134,9 +3134,9 @@ function Shell(props: ToolProps) {
when={command()}
fallback={
isRunning() || props.part.state.status === "streaming" ? (
<Spinner color={color()}>Writing command...</Spinner>
<Spinner color={color()}>Writing command</Spinner>
) : (
<text fg={theme.text.subdued}>Writing command...</text>
<text fg={theme.text.subdued}>Writing command</text>
)
}
>
@@ -3183,7 +3183,7 @@ function Write(props: ToolProps) {
</BlockTool>
</Match>
<Match when={true}>
<InlineTool icon="←" pending="Preparing write..." complete={stringValue(props.input.path)} part={props.part}>
<InlineTool icon="←" pending="Preparing write" complete={stringValue(props.input.path)} part={props.part}>
Write {pathFormatter.format(stringValue(props.input.path))}
</InlineTool>
</Match>
@@ -3194,7 +3194,7 @@ function Write(props: ToolProps) {
function Glob(props: ToolProps) {
const pathFormatter = usePathFormatter()
return (
<InlineTool icon="✱" pending="Finding files..." complete={stringValue(props.input.pattern)} part={props.part}>
<InlineTool icon="✱" pending="Finding files" complete={stringValue(props.input.pattern)} part={props.part}>
Glob "{stringValue(props.input.pattern)}"{" "}
<Show when={stringValue(props.input.path)}>in {pathFormatter.format(stringValue(props.input.path))} </Show>
<Show when={finiteNumber(props.metadata.count)}>
@@ -3218,7 +3218,7 @@ function Read(props: ToolProps) {
<>
<InlineTool
icon="→"
pending="Reading file..."
pending="Reading file"
complete={stringValue(props.input.path)}
spinner={isRunning()}
part={props.part}
@@ -3241,7 +3241,7 @@ function Read(props: ToolProps) {
function Grep(props: ToolProps) {
const pathFormatter = usePathFormatter()
return (
<InlineTool icon="✱" pending="Searching content..." complete={stringValue(props.input.pattern)} part={props.part}>
<InlineTool icon="✱" pending="Searching content" complete={stringValue(props.input.pattern)} part={props.part}>
Grep "{stringValue(props.input.pattern)}"{" "}
<Show when={stringValue(props.input.path)}>in {pathFormatter.format(stringValue(props.input.path))} </Show>
<Show when={finiteNumber(props.metadata.matches)}>
@@ -3253,7 +3253,7 @@ function Grep(props: ToolProps) {
function WebFetch(props: ToolProps) {
return (
<InlineTool icon="%" pending="Fetching from the web..." complete={stringValue(props.input.url)} part={props.part}>
<InlineTool icon="%" pending="Fetching from the web" complete={stringValue(props.input.url)} part={props.part}>
WebFetch {stringValue(props.input.url)}
</InlineTool>
)
@@ -3261,7 +3261,7 @@ function WebFetch(props: ToolProps) {
function WebSearch(props: ToolProps) {
return (
<InlineTool icon="◈" pending="Searching web..." complete={stringValue(props.input.query)} part={props.part}>
<InlineTool icon="◈" pending="Searching web" complete={stringValue(props.input.query)} part={props.part}>
{webSearchProviderLabel(props.metadata.provider)} "{stringValue(props.input.query)}"
</InlineTool>
)
@@ -3283,7 +3283,7 @@ function Subagent(props: ToolProps) {
icon={continuation() ? "↳" : isRunning() ? "│" : props.part.state.status === "completed" ? "✓" : "│"}
spinner={!continuation() && isRunning()}
complete={description()}
pending="Delegating..."
pending="Delegating"
part={props.part}
onClick={() => {
const id = sessionID()
@@ -3474,7 +3474,7 @@ function Edit(props: ToolProps) {
? { label: "← Edit", value: pathFormatter.format(stringValue(props.input.path)) }
: undefined
}
title={stringValue(props.input.path) ? undefined : "# Preparing edit..."}
title={stringValue(props.input.path) ? undefined : "# Preparing edit"}
part={props.part}
spinner={props.part.state.status === "streaming"}
/>
@@ -3629,7 +3629,7 @@ function Question(props: ToolProps) {
</BlockTool>
</Match>
<Match when={true}>
<InlineTool icon="→" pending="Asking questions..." complete={count()} part={props.part}>
<InlineTool icon="→" pending="Asking questions" complete={count()} part={props.part}>
Asked {count()} question{count() !== 1 ? "s" : ""}
</InlineTool>
</Match>
@@ -3640,7 +3640,7 @@ function Question(props: ToolProps) {
function Skill(props: ToolProps) {
const name = createMemo(() => stringValue(props.metadata.name) ?? stringValue(props.input.id))
return (
<InlineTool icon="→" pending="Loading skill..." complete={name()} part={props.part}>
<InlineTool icon="→" pending="Loading skill" complete={name()} part={props.part}>
Skill "{name()}"
</InlineTool>
)
+2 -2
View File
@@ -100,11 +100,11 @@ export function DialogPrompt(props: DialogPromptProps) {
cursorStyle={config.cursor}
/>
<Show when={props.busy}>
<Spinner color={theme.text.subdued}>{props.busyText ?? "Working..."}</Spinner>
<Spinner color={theme.text.subdued}>{props.busyText ?? "Working"}</Spinner>
</Show>
</box>
<box paddingBottom={1} gap={1} flexDirection="row">
<Show when={!props.busy} fallback={<text fg={theme.text.subdued}>processing...</text>}>
<Show when={!props.busy} fallback={<text fg={theme.text.subdued}>processing</text>}>
<Show when={shortcuts.get("dialog.prompt.submit")}>
<text fg={theme.text.default}>
{shortcuts.get("dialog.prompt.submit")} <span style={{ fg: theme.text.subdued }}>submit</span>
@@ -59,7 +59,7 @@ describe("DiffViewerFileTree", () => {
<ThemedDiffViewerFileTree width={32} files={[]} loading={false} error={undefined} />
))
expect(loading).not.toContain("Loading diff...")
expect(loading).not.toContain("Loading diff")
expect(loading).not.toContain("No files")
expect(failed).not.toContain("Failed to load diff")
expect(failed).not.toContain("No files")
@@ -73,7 +73,7 @@ function Fixture(props: { errorExpanded?: boolean }) {
function FailedPendingToolFixture() {
return (
<InlineToolRow icon="%" complete={false} pending="Preparing patch..." failed={true} failure="Patch failed">
<InlineToolRow icon="%" complete={false} pending="Preparing patch" failed={true} failure="Patch failed">
Patch
</InlineToolRow>
)
@@ -81,7 +81,7 @@ function FailedPendingToolFixture() {
function FailedCompleteToolFixture() {
return (
<InlineToolRow icon="→" complete={true} pending="Reading file..." failed={true} failure="Read failed">
<InlineToolRow icon="→" complete={true} pending="Reading file" failed={true} failure="Read failed">
Read src/index.ts
</InlineToolRow>
)