From 683d234d805e4d1097751d3cd583117856e41de5 Mon Sep 17 00:00:00 2001
From: Akshar Patel <123344143+AksharP5@users.noreply.github.com>
Date: Thu, 5 Feb 2026 21:11:08 -0500
Subject: [PATCH] feat(tui): highlight esc label on hover in dialog (#12383)
---
.../cli/cmd/tui/component/dialog-provider.tsx | 14 +++++++++++---
.../src/cli/cmd/tui/component/dialog-status.tsx | 16 ++++++++++++----
.../opencode/src/cli/cmd/tui/ui/dialog-alert.tsx | 15 ++++++++++++---
.../src/cli/cmd/tui/ui/dialog-confirm.tsx | 16 ++++++++++++----
.../src/cli/cmd/tui/ui/dialog-export-options.tsx | 16 ++++++++++++----
.../opencode/src/cli/cmd/tui/ui/dialog-help.tsx | 15 ++++++++++++---
.../src/cli/cmd/tui/ui/dialog-prompt.tsx | 16 ++++++++++++----
.../src/cli/cmd/tui/ui/dialog-select.tsx | 16 ++++++++++++----
8 files changed, 95 insertions(+), 29 deletions(-)
diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx
index 93e76cbdf..f8be5577b 100644
--- a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx
@@ -124,6 +124,7 @@ function AutoMethod(props: AutoMethodProps) {
const dialog = useDialog()
const sync = useSync()
const toast = useToast()
+ const [hover, setHover] = createSignal(false)
useKeyboard((evt) => {
if (evt.name === "c" && !evt.ctrl && !evt.meta) {
@@ -154,9 +155,16 @@ function AutoMethod(props: AutoMethodProps) {
{props.title}
- dialog.clear()}>
- esc
-
+ setHover(true)}
+ onMouseOut={() => setHover(false)}
+ onMouseUp={() => dialog.clear()}
+ >
+ esc
+
diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-status.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-status.tsx
index 3e6e30951..e2ab579a9 100644
--- a/packages/opencode/src/cli/cmd/tui/component/dialog-status.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/dialog-status.tsx
@@ -2,7 +2,7 @@ import { TextAttributes } from "@opentui/core"
import { useTheme } from "../context/theme"
import { useDialog } from "@tui/ui/dialog"
import { useSync } from "@tui/context/sync"
-import { For, Match, Switch, Show, createMemo } from "solid-js"
+import { For, Match, Switch, Show, createMemo, createSignal } from "solid-js"
import { Installation } from "@/installation"
export type DialogStatusProps = {}
@@ -11,6 +11,7 @@ export function DialogStatus() {
const sync = useSync()
const { theme } = useTheme()
const dialog = useDialog()
+ const [hover, setHover] = createSignal(false)
const enabledFormatters = createMemo(() => sync.data.formatter.filter((f) => f.enabled))
@@ -45,9 +46,16 @@ export function DialogStatus() {
Status
- dialog.clear()}>
- esc
-
+ setHover(true)}
+ onMouseOut={() => setHover(false)}
+ onMouseUp={() => dialog.clear()}
+ >
+ esc
+
OpenCode v{Installation.VERSION}
0} fallback={No MCP Servers}>
diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-alert.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-alert.tsx
index 642c73b48..8b4b61476 100644
--- a/packages/opencode/src/cli/cmd/tui/ui/dialog-alert.tsx
+++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-alert.tsx
@@ -2,6 +2,7 @@ import { TextAttributes } from "@opentui/core"
import { useTheme } from "../context/theme"
import { useDialog, type DialogContext } from "./dialog"
import { useKeyboard } from "@opentui/solid"
+import { createSignal } from "solid-js"
export type DialogAlertProps = {
title: string
@@ -12,6 +13,7 @@ export type DialogAlertProps = {
export function DialogAlert(props: DialogAlertProps) {
const dialog = useDialog()
const { theme } = useTheme()
+ const [hover, setHover] = createSignal(false)
useKeyboard((evt) => {
if (evt.name === "return") {
@@ -25,9 +27,16 @@ export function DialogAlert(props: DialogAlertProps) {
{props.title}
- dialog.clear()}>
- esc
-
+ setHover(true)}
+ onMouseOut={() => setHover(false)}
+ onMouseUp={() => dialog.clear()}
+ >
+ esc
+
{props.message}
diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-confirm.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-confirm.tsx
index b86bd4325..7d9b74cde 100644
--- a/packages/opencode/src/cli/cmd/tui/ui/dialog-confirm.tsx
+++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-confirm.tsx
@@ -2,7 +2,7 @@ import { TextAttributes } from "@opentui/core"
import { useTheme } from "../context/theme"
import { useDialog, type DialogContext } from "./dialog"
import { createStore } from "solid-js/store"
-import { For } from "solid-js"
+import { createSignal, For } from "solid-js"
import { useKeyboard } from "@opentui/solid"
import { Locale } from "@/util/locale"
@@ -16,6 +16,7 @@ export type DialogConfirmProps = {
export function DialogConfirm(props: DialogConfirmProps) {
const dialog = useDialog()
const { theme } = useTheme()
+ const [hover, setHover] = createSignal(false)
const [store, setStore] = createStore({
active: "confirm" as "confirm" | "cancel",
})
@@ -37,9 +38,16 @@ export function DialogConfirm(props: DialogConfirmProps) {
{props.title}
- dialog.clear()}>
- esc
-
+ setHover(true)}
+ onMouseOut={() => setHover(false)}
+ onMouseUp={() => dialog.clear()}
+ >
+ esc
+
{props.message}
diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-export-options.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-export-options.tsx
index 1e8d09bb0..957467c5d 100644
--- a/packages/opencode/src/cli/cmd/tui/ui/dialog-export-options.tsx
+++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-export-options.tsx
@@ -2,7 +2,7 @@ import { TextareaRenderable, TextAttributes } from "@opentui/core"
import { useTheme } from "../context/theme"
import { useDialog, type DialogContext } from "./dialog"
import { createStore } from "solid-js/store"
-import { onMount, Show, type JSX } from "solid-js"
+import { createSignal, onMount, Show, type JSX } from "solid-js"
import { useKeyboard } from "@opentui/solid"
export type DialogExportOptionsProps = {
@@ -25,6 +25,7 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
const dialog = useDialog()
const { theme } = useTheme()
let textarea: TextareaRenderable
+ const [hover, setHover] = createSignal(false)
const [store, setStore] = createStore({
thinking: props.defaultThinking,
toolDetails: props.defaultToolDetails,
@@ -80,9 +81,16 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
Export Options
- dialog.clear()}>
- esc
-
+ setHover(true)}
+ onMouseOut={() => setHover(false)}
+ onMouseUp={() => dialog.clear()}
+ >
+ esc
+
diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-help.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-help.tsx
index 4e4527930..f56347d4a 100644
--- a/packages/opencode/src/cli/cmd/tui/ui/dialog-help.tsx
+++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-help.tsx
@@ -3,11 +3,13 @@ import { useTheme } from "@tui/context/theme"
import { useDialog } from "./dialog"
import { useKeyboard } from "@opentui/solid"
import { useKeybind } from "@tui/context/keybind"
+import { createSignal } from "solid-js"
export function DialogHelp() {
const dialog = useDialog()
const { theme } = useTheme()
const keybind = useKeybind()
+ const [hover, setHover] = createSignal(false)
useKeyboard((evt) => {
if (evt.name === "return" || evt.name === "escape") {
@@ -21,9 +23,16 @@ export function DialogHelp() {
Help
- dialog.clear()}>
- esc/enter
-
+ setHover(true)}
+ onMouseOut={() => setHover(false)}
+ onMouseUp={() => dialog.clear()}
+ >
+ esc/enter
+
diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx
index b1b05a0f1..03814e17d 100644
--- a/packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx
+++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx
@@ -1,7 +1,7 @@
import { TextareaRenderable, TextAttributes } from "@opentui/core"
import { useTheme } from "../context/theme"
import { useDialog, type DialogContext } from "./dialog"
-import { onMount, type JSX } from "solid-js"
+import { createSignal, onMount, type JSX } from "solid-js"
import { useKeyboard } from "@opentui/solid"
export type DialogPromptProps = {
@@ -17,6 +17,7 @@ export function DialogPrompt(props: DialogPromptProps) {
const dialog = useDialog()
const { theme } = useTheme()
let textarea: TextareaRenderable
+ const [hover, setHover] = createSignal(false)
useKeyboard((evt) => {
if (evt.name === "return") {
@@ -39,9 +40,16 @@ export function DialogPrompt(props: DialogPromptProps) {
{props.title}
- dialog.clear()}>
- esc
-
+ setHover(true)}
+ onMouseOut={() => setHover(false)}
+ onMouseUp={() => dialog.clear()}
+ >
+ esc
+
{props.description}
diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx
index 490a10072..7792900bc 100644
--- a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx
+++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx
@@ -1,7 +1,7 @@
import { InputRenderable, RGBA, ScrollBoxRenderable, TextAttributes } from "@opentui/core"
import { useTheme, selectedForeground } from "@tui/context/theme"
import { entries, filter, flatMap, groupBy, pipe, take } from "remeda"
-import { batch, createEffect, createMemo, For, Show, type JSX, on } from "solid-js"
+import { batch, createEffect, createMemo, createSignal, For, Show, type JSX, on } from "solid-js"
import { createStore } from "solid-js/store"
import { useKeyboard, useTerminalDimensions } from "@opentui/solid"
import * as fuzzysort from "fuzzysort"
@@ -49,6 +49,7 @@ export type DialogSelectRef = {
export function DialogSelect(props: DialogSelectProps) {
const dialog = useDialog()
const { theme } = useTheme()
+ const [hover, setHover] = createSignal(false)
const [store, setStore] = createStore({
selected: 0,
filter: "",
@@ -226,9 +227,16 @@ export function DialogSelect(props: DialogSelectProps) {
{props.title}
- dialog.clear()}>
- esc
-
+ setHover(true)}
+ onMouseOut={() => setHover(false)}
+ onMouseUp={() => dialog.clear()}
+ >
+ esc
+