diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
index bbd5eac1a2..6f18f02a98 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
@@ -1742,6 +1742,7 @@ function InlineTool(props: {
const sync = useSync()
const renderer = useRenderer()
const [hover, setHover] = createSignal(false)
+ const [errorExpanded, setErrorExpanded] = createSignal(false)
const permission = createMemo(() => {
const callID = sync.data.permission[ctx.sessionID]?.at(0)?.tool?.callID
@@ -1749,13 +1750,6 @@ function InlineTool(props: {
return callID === props.part.callID
})
- const fg = createMemo(() => {
- if (permission()) return theme.warning
- if (hover() && props.onClick) return theme.text
- if (props.complete) return theme.textMuted
- return theme.text
- })
-
const error = createMemo(() => (props.part.state.status === "error" ? props.part.state.error : undefined))
const denied = createMemo(
@@ -1766,14 +1760,28 @@ function InlineTool(props: {
error()?.includes("user dismissed"),
)
+ const failed = createMemo(() => Boolean(error() && !denied()))
+ const clickable = createMemo(() => Boolean(props.onClick || failed()))
+ const fg = createMemo(() => {
+ if (permission()) return theme.warning
+ if (failed()) return theme.error
+ if (hover() && props.onClick) return theme.text
+ if (props.complete) return theme.textMuted
+ return theme.text
+ })
+
return (
props.onClick && setHover(true)}
+ onMouseOver={() => clickable() && setHover(true)}
onMouseOut={() => setHover(false)}
onMouseUp={() => {
if (renderer.getSelection()?.getSelectedText()) return
+ if (failed()) {
+ setErrorExpanded((value) => !value)
+ return
+ }
props.onClick?.()
}}
renderBefore={function () {
@@ -1804,19 +1812,23 @@ function InlineTool(props: {
{props.icon}
-
+
{props.children}
-
+
{error()}
diff --git a/packages/opencode/test/cli/tui/__snapshots__/inline-tool-wrap-snapshot.test.tsx.snap b/packages/opencode/test/cli/tui/__snapshots__/inline-tool-wrap-snapshot.test.tsx.snap
index 89987234dd..6cb0df1e2e 100644
--- a/packages/opencode/test/cli/tui/__snapshots__/inline-tool-wrap-snapshot.test.tsx.snap
+++ b/packages/opencode/test/cli/tui/__snapshots__/inline-tool-wrap-snapshot.test.tsx.snap
@@ -1,6 +1,16 @@
// Bun Snapshot v1, https://bun.sh/docs/test/snapshots
exports[`TUI inline tool wrapping snapshots consecutive grep, glob, and read rows at a narrow width 1`] = `
+" ✱ Grep "OPENCODE.*DB|database|sqlite|drizzle|dev.*db|data.
+ *dir|xdg|APPDATA" in packages/opencode/src (151 matches)
+ ✱ Glob "**/*db*" in packages/opencode (6 matches)
+ → Read packages/opencode/src/storage/db.ts [offset=1, limit=130]
+ → Read packages/opencode/src/index.ts [offset=1, limit=100]
+ ✱ Grep "export const OPENCODE_DB|OPENCODE_DB|OPENCODE_DEV|Global\\.
+ Path\\.data|data =" in packages/opencode/src (115 matches)"
+`;
+
+exports[`TUI inline tool wrapping snapshots expanded tool errors under the tool text 1`] = `
" ✱ Grep "OPENCODE.*DB|database|sqlite|drizzle|dev.*db|data.
*dir|xdg|APPDATA" in packages/opencode/src (151 matches)
✱ Glob "**/*db*" in packages/opencode (6 matches)
diff --git a/packages/opencode/test/cli/tui/inline-tool-wrap-snapshot.test.tsx b/packages/opencode/test/cli/tui/inline-tool-wrap-snapshot.test.tsx
index 318847e946..c7aa4708ec 100644
--- a/packages/opencode/test/cli/tui/inline-tool-wrap-snapshot.test.tsx
+++ b/packages/opencode/test/cli/tui/inline-tool-wrap-snapshot.test.tsx
@@ -39,7 +39,7 @@ const tools: readonly ToolFixture[] = [
},
] as const
-function InlineToolRow(props: { item: ToolFixture }) {
+function InlineToolRow(props: { item: ToolFixture; errorExpanded?: boolean }) {
const [margin, setMargin] = createSignal(0)
return (
@@ -56,7 +56,7 @@ function InlineToolRow(props: { item: ToolFixture }) {
{props.item.icon}
{props.item.label}
- {props.item.error && (
+ {props.item.error && props.errorExpanded && (
{props.item.error}
@@ -65,11 +65,11 @@ function InlineToolRow(props: { item: ToolFixture }) {
)
}
-function Fixture() {
+function Fixture(props: { errorExpanded?: boolean }) {
return (
- {(item) => }
+ {(item) => }
)
@@ -90,4 +90,19 @@ describe("TUI inline tool wrapping", () => {
.trimEnd(),
).toMatchSnapshot()
})
+
+ test("snapshots expanded tool errors under the tool text", async () => {
+ testSetup = await testRender(() => , { width: 72, height: 12 })
+ await testSetup.renderOnce()
+ await testSetup.renderOnce()
+
+ expect(
+ testSetup
+ .captureCharFrame()
+ .split("\n")
+ .map((line) => line.trimEnd())
+ .join("\n")
+ .trimEnd(),
+ ).toMatchSnapshot()
+ })
})