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 245a7296b5..f9caef33c1 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
@@ -1802,11 +1802,27 @@ function InlineTool(props: {
-
- ~ {props.pending}>} when={props.complete}>
- {props.icon} {props.children}
-
-
+
+ ~ {props.pending}
+
+ }
+ when={props.complete}
+ >
+
+
+ {props.icon}
+
+
+ {props.children}
+
+
+
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
new file mode 100644
index 0000000000..664fa4e3f5
--- /dev/null
+++ b/packages/opencode/test/cli/tui/__snapshots__/inline-tool-wrap-snapshot.test.tsx.snap
@@ -0,0 +1,58 @@
+// 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`] = `
+"CURRENT: measured height adds top margin after wrapped rows
+
+ * 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)
+
+
+STABLE WRAP: no height-coupled margin
+ * 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)
+
+
+HANGING INDENT: wrap aligns with tool text
+ * 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)
+
+
+DETAIL ROWS: split identity from metadata
+ * Grep "OPENCODE.*DB|database|sqlite|drizzle|dev.*db|data.
+ *dir|xdg|APPDATA"
+ packages/opencode/src - 151 matches
+ * Glob "**/*db*"
+ 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 ="
+ packages/opencode/src - 115 matches
+
+
+COMPACT: truncate middle, never wrap
+ * Grep "OPENCODE.*DB|d...dir|xdg|APPDATA" - packages/openc...c - 151
+ * Glob "**/*db*" - packages/opencode - 6 matches
+ -> Read packages/openco...rc/storage/db.ts - offset=1, limit=130
+ -> Read packages/opencode/src/index.ts - offset=1, limit=100
+ * Grep "export const O...th\\.data|data =" - packages/openc...c - 115"
+`;
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
new file mode 100644
index 0000000000..0497fc462e
--- /dev/null
+++ b/packages/opencode/test/cli/tui/inline-tool-wrap-snapshot.test.tsx
@@ -0,0 +1,174 @@
+import { afterEach, describe, expect, test } from "bun:test"
+import { createSignal, For } from "solid-js"
+import { testRender } from "@opentui/solid"
+
+let testSetup: Awaited> | undefined
+
+afterEach(() => {
+ testSetup?.renderer.destroy()
+ testSetup = undefined
+})
+
+const tools = [
+ {
+ kind: "grep",
+ icon: "*",
+ label:
+ 'Grep "OPENCODE.*DB|database|sqlite|drizzle|dev.*db|data.*dir|xdg|APPDATA" in packages/opencode/src (151 matches)',
+ target: '"OPENCODE.*DB|database|sqlite|drizzle|dev.*db|data.*dir|xdg|APPDATA"',
+ meta: "packages/opencode/src - 151 matches",
+ },
+ {
+ kind: "glob",
+ icon: "*",
+ label: 'Glob "**/*db*" in packages/opencode (6 matches)',
+ target: '"**/*db*"',
+ meta: "packages/opencode - 6 matches",
+ },
+ {
+ kind: "read",
+ icon: "->",
+ label: "Read packages/opencode/src/storage/db.ts [offset=1, limit=130]",
+ target: "packages/opencode/src/storage/db.ts",
+ meta: "offset=1, limit=130",
+ },
+ {
+ kind: "read",
+ icon: "->",
+ label: "Read packages/opencode/src/index.ts [offset=1, limit=100]",
+ target: "packages/opencode/src/index.ts",
+ meta: "offset=1, limit=100",
+ },
+ {
+ kind: "grep",
+ icon: "*",
+ label:
+ 'Grep "export const OPENCODE_DB|OPENCODE_DB|OPENCODE_DEV|Global\\.Path\\.data|data =" in packages/opencode/src (115 matches)',
+ target: '"export const OPENCODE_DB|OPENCODE_DB|OPENCODE_DEV|Global\\.Path\\.data|data ="',
+ meta: "packages/opencode/src - 115 matches",
+ },
+] as const
+
+function CurrentInlineRow(props: { item: (typeof tools)[number]; index: number }) {
+ const [margin, setMargin] = createSignal(0)
+
+ return (
+ 1) {
+ setMargin(1)
+ return
+ }
+ const previous = parent.getChildren()[parent.getChildren().indexOf(this) - 1]
+ if (!previous) {
+ setMargin(0)
+ return
+ }
+ if (previous.height > 1 || previous.id.startsWith("text-")) setMargin(1)
+ }}
+ >
+
+ {props.item.icon} {props.item.label}
+
+
+ )
+}
+
+function StableInlineRow(props: { item: (typeof tools)[number] }) {
+ return (
+
+
+ {props.item.icon} {props.item.label}
+
+
+ )
+}
+
+function HangingIndentRow(props: { item: (typeof tools)[number] }) {
+ return (
+
+ {props.item.icon}
+ {props.item.label}
+
+ )
+}
+
+function DetailRow(props: { item: (typeof tools)[number] }) {
+ return (
+
+
+ {props.item.icon} {titlecase(props.item.kind)} {props.item.target}
+
+ {props.item.meta}
+
+ )
+}
+
+function CompactRow(props: { item: (typeof tools)[number] }) {
+ return (
+
+
+ {props.item.icon} {titlecase(props.item.kind)} {truncateMiddle(props.item.target, 34)} -{" "}
+ {truncateMiddle(props.item.meta, 32)}
+
+
+ )
+}
+
+function Fixture() {
+ return (
+
+ CURRENT: measured height adds top margin after wrapped rows
+
+ {(item, index) => }
+
+ STABLE WRAP: no height-coupled margin
+
+ {(item) => }
+
+ HANGING INDENT: wrap aligns with tool text
+
+ {(item) => }
+
+ DETAIL ROWS: split identity from metadata
+
+ {(item) => }
+
+ COMPACT: truncate middle, never wrap
+
+ {(item) => }
+
+
+ )
+}
+
+describe("TUI inline tool wrapping", () => {
+ test("snapshots consecutive grep, glob, and read rows at a narrow width", async () => {
+ testSetup = await testRender(() => , { width: 72, height: 60 })
+ await testSetup.renderOnce()
+ await testSetup.renderOnce()
+
+ expect(
+ testSetup
+ .captureCharFrame()
+ .split("\n")
+ .map((line) => line.trimEnd())
+ .join("\n")
+ .trimEnd(),
+ ).toMatchSnapshot()
+ })
+})
+
+function titlecase(value: string) {
+ return value.slice(0, 1).toUpperCase() + value.slice(1)
+}
+
+function truncateMiddle(value: string, max: number) {
+ if (value.length <= max) return value
+ return value.slice(0, Math.floor((max - 3) / 2)) + "..." + value.slice(value.length - Math.ceil((max - 3) / 2))
+}