From b784c923a8eeab52412eaebb9a44ad05a1411165 Mon Sep 17 00:00:00 2001 From: David Hill Date: Tue, 17 Feb 2026 16:51:48 +0000 Subject: [PATCH] tweak(ui): bump button heights and align permission prompt layout --- .../src/pages/session/session-prompt-dock.tsx | 128 ++++--- .../cli/cmd/tui/routes/session/permission.tsx | 318 +++++++++++++----- packages/ui/src/components/button.css | 6 +- packages/ui/src/components/message-part.css | 21 ++ packages/ui/src/components/message-part.tsx | 6 +- 5 files changed, 344 insertions(+), 135 deletions(-) diff --git a/packages/app/src/pages/session/session-prompt-dock.tsx b/packages/app/src/pages/session/session-prompt-dock.tsx index 2b9571982..2bdb4ee2c 100644 --- a/packages/app/src/pages/session/session-prompt-dock.tsx +++ b/packages/app/src/pages/session/session-prompt-dock.tsx @@ -1,7 +1,6 @@ import { For, Show, createEffect, createMemo, createSignal, on, onCleanup } from "solid-js" import type { QuestionRequest, Todo } from "@opencode-ai/sdk/v2" import { Button } from "@opencode-ai/ui/button" -import { BasicTool } from "@opencode-ai/ui/basic-tool" import { PromptInput } from "@/components/prompt-input" import { QuestionDock } from "@/components/question-dock" import { SessionTodoDock } from "@/components/session-todo-dock" @@ -123,63 +122,84 @@ export function SessionPromptDock(props: { - {(perm) => ( -
- - 0}> -
- - {(pattern) => {pattern}} - + {(perm) => { + const toolTitle = () => { + const key = `settings.permissions.tool.${perm.permission}.title` + const value = props.t(key) + if (value === key) return perm.permission + return value + } + + const toolDescription = () => { + const key = `settings.permissions.tool.${perm.permission}.description` + const value = props.t(key) + if (value === key) return "" + return value + } + + return ( +
+
+
+
+
+ {props.t("notification.permission.title")}{" "} + {toolTitle()} +
+
+ +
+ +
{toolDescription()}
+
+ + 0}> +
+ + {(pattern) => ( +
+ {pattern} +
+ )} +
+
+
+
- - -
- {props.t("settings.permissions.tool.doom_loop.description")} + +
+
+
+ + + +
- - -
-
- - -
-
- )} + ) + }} diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx index 9e79c76bf..9b18c3445 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx @@ -64,10 +64,6 @@ function EditBody(props: { request: PermissionRequest }) { return ( - - {"→"} - Edit {normalizePath(filepath())} - + + + No diff provided + + ) } @@ -194,76 +195,233 @@ export function PermissionPrompt(props: { request: PermissionRequest }) { {(() => { + const info = () => { + const permission = props.request.permission + const data = input() + + if (permission === "edit") { + const raw = props.request.metadata?.filepath + const filepath = typeof raw === "string" ? raw : "" + return { + icon: "→", + title: `Edit ${normalizePath(filepath)}`, + body: , + } + } + + if (permission === "read") { + const raw = data.filePath + const filePath = typeof raw === "string" ? raw : "" + return { + icon: "→", + title: `Read ${normalizePath(filePath)}`, + body: ( + + + {"Path: " + normalizePath(filePath)} + + + ), + } + } + + if (permission === "glob") { + const pattern = typeof data.pattern === "string" ? data.pattern : "" + return { + icon: "✱", + title: `Glob "${pattern}"`, + body: ( + + + {"Pattern: " + pattern} + + + ), + } + } + + if (permission === "grep") { + const pattern = typeof data.pattern === "string" ? data.pattern : "" + return { + icon: "✱", + title: `Grep "${pattern}"`, + body: ( + + + {"Pattern: " + pattern} + + + ), + } + } + + if (permission === "list") { + const raw = data.path + const dir = typeof raw === "string" ? raw : "" + return { + icon: "→", + title: `List ${normalizePath(dir)}`, + body: ( + + + {"Path: " + normalizePath(dir)} + + + ), + } + } + + if (permission === "bash") { + const title = + typeof data.description === "string" && data.description ? data.description : "Shell command" + const command = typeof data.command === "string" ? data.command : "" + return { + icon: "#", + title, + body: ( + + + {"$ " + command} + + + ), + } + } + + if (permission === "task") { + const type = typeof data.subagent_type === "string" ? data.subagent_type : "Unknown" + const desc = typeof data.description === "string" ? data.description : "" + return { + icon: "#", + title: `${Locale.titlecase(type)} Task`, + body: ( + + + {"◉ " + desc} + + + ), + } + } + + if (permission === "webfetch") { + const url = typeof data.url === "string" ? data.url : "" + return { + icon: "%", + title: `WebFetch ${url}`, + body: ( + + + {"URL: " + url} + + + ), + } + } + + if (permission === "websearch") { + const query = typeof data.query === "string" ? data.query : "" + return { + icon: "◈", + title: `Exa Web Search "${query}"`, + body: ( + + + {"Query: " + query} + + + ), + } + } + + if (permission === "codesearch") { + const query = typeof data.query === "string" ? data.query : "" + return { + icon: "◇", + title: `Exa Code Search "${query}"`, + body: ( + + + {"Query: " + query} + + + ), + } + } + + if (permission === "external_directory") { + const meta = props.request.metadata ?? {} + const parent = typeof meta["parentDir"] === "string" ? meta["parentDir"] : undefined + const filepath = typeof meta["filepath"] === "string" ? meta["filepath"] : undefined + const pattern = props.request.patterns?.[0] + const derived = + typeof pattern === "string" ? (pattern.includes("*") ? path.dirname(pattern) : pattern) : undefined + + const raw = parent ?? filepath ?? derived + const dir = normalizePath(raw) + const patterns = (props.request.patterns ?? []).filter((p): p is string => typeof p === "string") + + return { + icon: "←", + title: `Access external directory ${dir}`, + body: ( + 0}> + + Patterns + + {(p) => {"- " + p}} + + + + ), + } + } + + if (permission === "doom_loop") { + return { + icon: "⟳", + title: "Continue after repeated failures", + body: ( + + This keeps the session running despite repeated failures. + + ), + } + } + + return { + icon: "⚙", + title: `Call tool ${permission}`, + body: ( + + {"Tool: " + permission} + + ), + } + } + + const current = info() + + const header = () => ( + + + {"△"} + Permission required + + + + {current.icon} + + {current.title} + + + ) + const body = ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {(() => { - const meta = props.request.metadata ?? {} - const parent = typeof meta["parentDir"] === "string" ? meta["parentDir"] : undefined - const filepath = typeof meta["filepath"] === "string" ? meta["filepath"] : undefined - const pattern = props.request.patterns?.[0] - const derived = - typeof pattern === "string" - ? pattern.includes("*") - ? path.dirname(pattern) - : pattern - : undefined - - const raw = parent ?? filepath ?? derived - const dir = normalizePath(raw) - - return - })()} - - - - - - - - - } + header={header()} + body={current.body} options={{ once: "Allow once", always: "Allow always", reject: "Reject" }} escapeKey="reject" fullscreen @@ -372,6 +530,7 @@ function RejectPrompt(props: { onConfirm: (message: string) => void; onCancel: ( function Prompt>(props: { title: string + header?: JSX.Element body: JSX.Element options: T escapeKey?: keyof T @@ -445,10 +604,19 @@ function Prompt>(props: { })} > - - {"△"} - {props.title} - + + {"△"} + {props.title} + + } + > + + {props.header} + + {props.body}
- - -