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

This commit is contained in:
David Hill 2026-09-03 13:56:40 -06:00 committed by GitHub
parent d8eb3b80fb
commit 7561b4a050
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 34 additions and 34 deletions

View file

@ -465,7 +465,7 @@ function App(props: { onSnapshot?: () => Promise<string[]>; pluginHost: TuiPlugi
return
}
const title = session.title.length > 40 ? session.title.slice(0, 37) + "..." : session.title
const title = session.title.length > 40 ? session.title.slice(0, 37) + "…" : session.title
renderer.setTerminalTitle(`OC | ${title}`)
return
}
@ -1051,7 +1051,7 @@ function App(props: { onSnapshot?: () => Promise<string[]>; pluginHost: TuiPlugi
toast.show({
variant: "info",
message: `Updating to v${version}...`,
message: `Updating to v${version}…`,
duration: 30000,
})

View file

@ -51,7 +51,7 @@ export function DialogConsoleOrg() {
if (listed === undefined) {
return [
{
title: "Loading orgs...",
title: "Loading orgs…",
value: "loading",
onSelect: () => {},
},

View file

@ -113,7 +113,7 @@ export function DialogMoveSession(props: DialogMoveSessionProps) {
if (showError()) return []
const data = directoryData()
const current = currentRoot()?.directory
if (directories.loading && !data && !current) return [{ title: "Loading project directories...", value: undefined }]
if (directories.loading && !data && !current) return [{ title: "Loading project directories…", value: undefined }]
const roots = [...(data ?? [])]
if (current && !roots.some((item) => item.directory === current)) roots.unshift({ directory: current })
roots.sort((a, b) => {

View file

@ -297,7 +297,7 @@ function AutoMethod(props: AutoMethodProps) {
<Link href={props.authorization.url} fg={theme.primary} />
<text fg={theme.textMuted}>{props.authorization.instructions}</text>
</box>
<text fg={theme.textMuted}>Waiting for authorization...</text>
<text fg={theme.textMuted}>Waiting for authorization…</text>
<text fg={theme.text}>
c <span style={{ fg: theme.textMuted }}>copy</span>
</text>

View file

@ -51,7 +51,7 @@ export function DialogSkill(props: DialogSkillProps) {
return (
<DialogSelect
title="Skills"
placeholder="Search skills..."
placeholder="Search skills…"
options={options()}
renderFilter={!showError()}
locked={showError()}

View file

@ -39,7 +39,7 @@ export function DialogWorkspaceList() {
return {
title:
removing() === workspace.id
? "Deleting..."
? "Deleting…"
: deleting() === workspace.id
? `Delete ${workspace.name}? Press delete again`
: workspace.name,

View file

@ -219,7 +219,7 @@ function buildIssueURL(message: string, stack: 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```")

View file

@ -1313,10 +1313,10 @@ export function Prompt(props: PromptProps) {
if (store.mode === "shell") {
if (!shell().length) return undefined
const example = shell()[store.placeholder % shell().length]
return `Run a command... "${example}"`
return `Run a command… "${example}"`
}
if (!list().length) return undefined
return `Ask anything... "${list()[store.placeholder % list().length]}"`
return `Ask anything… "${list()[store.placeholder % list().length]}"`
})
const spinnerDef = createMemo(() => {
@ -1537,7 +1537,7 @@ export function Prompt(props: PromptProps) {
if (!r) return
if (r.message.includes("exceeded your current quota") && r.message.includes("gemini"))
return "gemini is way too hot right now"
if (r.message.length > 80) return r.message.slice(0, 80) + "..."
if (r.message.length > 80) return r.message.slice(0, 80) + "…"
return r.message
})
const isTruncated = createMemo(() => {

View file

@ -5,7 +5,7 @@ import { Spinner } from "./spinner"
export function StartupLoading(props: { ready: () => boolean }) {
const theme = useTheme().theme
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

View file

@ -766,7 +766,7 @@ function DiffViewer(props: { api: TuiPluginApi }) {
<Match when={diff.loading}>
<Separator axis="x" />
<box flexGrow={1} paddingLeft={1}>
<text fg={theme().textMuted}>Loading diff...</text>
<text fg={theme().textMuted}>Loading diff…</text>
</box>
</Match>
<Match when={!diff.loading && files().length === 0}>

View file

@ -49,7 +49,7 @@ function Install(props: { api: TuiPluginApi }) {
title="Install plugin"
placeholder="npm package name"
busy={busy()}
busyText="Installing plugin..."
busyText="Installing plugin…"
description={() => (
<box flexDirection="row" gap={1}>
<text fg={props.api.theme.current.textMuted}>scope:</text>

View file

@ -1812,7 +1812,7 @@ function GenericTool(props: ToolProps) {
<Show
when={props.output && ctx.showGenericToolOutput()}
fallback={
<InlineTool icon="⚙" pending="Writing command..." complete={true} part={props.part}>
<InlineTool icon="⚙" pending="Writing command…" complete={true} part={props.part}>
{props.tool} {input(props.input)}
</InlineTool>
}
@ -2094,7 +2094,7 @@ function Shell(props: ToolProps) {
</BlockTool>
</Match>
<Match when={true}>
<InlineTool icon="$" pending="Writing command..." complete={stringValue(props.input.command)} part={props.part}>
<InlineTool icon="$" pending="Writing command…" complete={stringValue(props.input.command)} part={props.part}>
{stringValue(props.input.command)}
</InlineTool>
</Match>
@ -2128,7 +2128,7 @@ function Write(props: ToolProps) {
<Match when={true}>
<InlineTool
icon="←"
pending="Preparing write..."
pending="Preparing write…"
complete={stringValue(props.input.filePath)}
part={props.part}
>
@ -2142,7 +2142,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={numberValue(props.metadata.count)}>
@ -2167,7 +2167,7 @@ function Read(props: ToolProps) {
<>
<InlineTool
icon="→"
pending="Reading file..."
pending="Reading file…"
complete={stringValue(props.input.filePath)}
spinner={isRunning()}
part={props.part}
@ -2190,7 +2190,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={numberValue(props.metadata.matches)}>
@ -2202,7 +2202,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>
)
@ -2210,7 +2210,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)}"{" "}
<Show when={numberValue(props.metadata.numResults)}>({numberValue(props.metadata.numResults)} results)</Show>
</InlineTool>
@ -2300,7 +2300,7 @@ function Task(props: ToolProps) {
color={retry() ? theme.error : undefined}
spinner={isRunning()}
complete={stringValue(props.input.description)}
pending="Delegating..."
pending="Delegating…"
part={props.part}
onClick={() => {
if (sessionID()) {
@ -2437,7 +2437,7 @@ function Edit(props: ToolProps) {
</BlockTool>
</Match>
<Match when={true}>
<InlineTool icon="←" pending="Preparing edit..." complete={stringValue(props.input.filePath)} part={props.part}>
<InlineTool icon="←" pending="Preparing edit…" complete={stringValue(props.input.filePath)} part={props.part}>
Edit {pathFormatter.format(stringValue(props.input.filePath))} {input({ replaceAll: props.input.replaceAll })}
</InlineTool>
</Match>
@ -2513,7 +2513,7 @@ function ApplyPatch(props: ToolProps) {
</For>
</Match>
<Match when={true}>
<InlineTool icon="%" pending="Preparing patch..." failure="Patch failed" complete={false} part={props.part}>
<InlineTool icon="%" pending="Preparing patch…" failure="Patch failed" complete={false} part={props.part}>
Patch
</InlineTool>
</Match>
@ -2535,12 +2535,12 @@ function TodoWrite(props: ToolProps) {
<Match when={true}>
<InlineTool
icon="⚙"
pending="Updating todos..."
pending="Updating todos…"
failure="Todo update failed"
complete={false}
part={props.part}
>
Updating todos...
Updating todos…
</InlineTool>
</Match>
</Switch>
@ -2575,7 +2575,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>
@ -2585,7 +2585,7 @@ function Question(props: ToolProps) {
function Skill(props: ToolProps) {
return (
<InlineTool icon="→" pending="Loading skill..." complete={stringValue(props.input.name)} part={props.part}>
<InlineTool icon="→" pending="Loading skill…" complete={stringValue(props.input.name)} part={props.part}>
Skill "{stringValue(props.input.name)}"
</InlineTool>
)

View file

@ -99,11 +99,11 @@ export function DialogPrompt(props: DialogPromptProps) {
cursorStyle={tuiConfig.cursor}
/>
<Show when={props.busy}>
<Spinner color={theme.textMuted}>{props.busyText ?? "Working..."}</Spinner>
<Spinner color={theme.textMuted}>{props.busyText ?? "Working…"}</Spinner>
</Show>
</box>
<box paddingBottom={1} gap={1} flexDirection="row">
<Show when={!props.busy} fallback={<text fg={theme.textMuted}>processing...</text>}>
<Show when={!props.busy} fallback={<text fg={theme.textMuted}>processing…</text>}>
<Show when={submitShortcut()}>
<text fg={theme.text}>
{submitShortcut()} <span style={{ fg: theme.textMuted }}>submit</span>

View file

@ -77,7 +77,7 @@ describe("DiffViewerFileTree", () => {
<DiffViewerFileTree width={32} files={[]} loading={false} error={undefined} theme={theme} />
))
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")

View file

@ -195,7 +195,7 @@ function StickyScrollFixture(props: { separated: boolean; scroll: (scroll: Scrol
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>
)
@ -203,7 +203,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>
)