From 2cc6630d401ff162c49412013ae86bcbcfb29ce2 Mon Sep 17 00:00:00 2001 From: Shantur Rathore Date: Wed, 17 Jun 2026 22:42:10 +0100 Subject: [PATCH] style(ui): render skill output like read Route the skill tool through the read renderer so skill output uses the same preview, language detection, copy, and wrapping chrome as read output while preserving a skill-specific title label. Keep skill collapsed by default with the same error-expands behavior as read, and include path/name inputs in read-style search text so skill content remains discoverable. Validated with npm run typecheck --workspace @codenomad/ui. --- packages/ui/src/components/tool-call.tsx | 2 +- .../components/tool-call/renderers/read.tsx | 21 +++++++++++++------ .../src/components/tool-call/search-text.ts | 2 ++ .../ui/src/components/tool-call/tool-title.ts | 1 + 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/packages/ui/src/components/tool-call.tsx b/packages/ui/src/components/tool-call.tsx index 3811b7f9..f03c1695 100644 --- a/packages/ui/src/components/tool-call.tsx +++ b/packages/ui/src/components/tool-call.tsx @@ -756,7 +756,7 @@ export default function ToolCall(props: ToolCallProps) { } const prefExpanded = toolOutputDefaultExpanded() const toolName = toolCallMemo()?.tool || "" - if (toolName === "read") { + if (toolName === "read" || toolName === "skill") { const state = toolState() if (state?.status === "error") { return true diff --git a/packages/ui/src/components/tool-call/renderers/read.tsx b/packages/ui/src/components/tool-call/renderers/read.tsx index 733d2e18..a6aef269 100644 --- a/packages/ui/src/components/tool-call/renderers/read.tsx +++ b/packages/ui/src/components/tool-call/renderers/read.tsx @@ -3,15 +3,23 @@ import { ensureMarkdownContent, getRelativePath, getToolName, inferLanguageFromP import { tGlobal } from "../../../lib/i18n" import { getReadToolSearchText } from "../search-text" +function getReadPath(input: Record): string { + const filePath = typeof input.filePath === "string" ? input.filePath : "" + if (filePath) return filePath + const path = typeof input.path === "string" ? input.path : "" + if (path) return path + return typeof input.name === "string" ? input.name : "" +} + export const readRenderer: ToolRenderer = { - tools: ["read"], + tools: ["read", "skill"], getSearchText: getReadToolSearchText, getAction: () => tGlobal("toolCall.renderer.action.readingFile"), - getTitle({ toolState }) { + getTitle({ toolName, toolState }) { const state = toolState() if (!state) return undefined const { input } = readToolStatePayload(state) - const filePath = typeof input.filePath === "string" ? input.filePath : "" + const filePath = getReadPath(input) const offset = typeof input.offset === "number" ? input.offset : undefined const limit = typeof input.limit === "number" ? input.limit : undefined const relativePath = filePath ? getRelativePath(filePath) : "" @@ -25,7 +33,8 @@ export const readRenderer: ToolRenderer = { detailParts.push(tGlobal("toolCall.renderer.read.detail.limit", { limit })) } - const baseTitle = relativePath ? `${getToolName("read")} ${relativePath}` : getToolName("read") + const toolLabel = getToolName(toolName()) + const baseTitle = relativePath ? `${toolLabel} ${relativePath}` : toolLabel if (!detailParts.length) { return baseTitle } @@ -38,7 +47,7 @@ export const readRenderer: ToolRenderer = { const { metadata, input } = readToolStatePayload(state) const preview = typeof metadata.preview === "string" ? metadata.preview : null if (!preview) return undefined - const language = inferLanguageFromPath(typeof input.filePath === "string" ? input.filePath : undefined) ?? "text" + const language = inferLanguageFromPath(getReadPath(input)) ?? "text" return { language, copyText: preview, wrapToggle: true, suppressInnerHeader: true } }, renderBody({ toolState, renderMarkdown }) { @@ -46,7 +55,7 @@ export const readRenderer: ToolRenderer = { if (!state || state.status === "pending") return null const { metadata, input } = readToolStatePayload(state) const preview = typeof metadata.preview === "string" ? metadata.preview : null - const language = inferLanguageFromPath(typeof input.filePath === "string" ? input.filePath : undefined) + const language = inferLanguageFromPath(getReadPath(input)) const content = ensureMarkdownContent(preview, language, true) if (!content) return null return renderMarkdown({ content, disableHighlight: state.status === "running" }) diff --git a/packages/ui/src/components/tool-call/search-text.ts b/packages/ui/src/components/tool-call/search-text.ts index 314e7daf..b50cf4ef 100644 --- a/packages/ui/src/components/tool-call/search-text.ts +++ b/packages/ui/src/components/tool-call/search-text.ts @@ -75,6 +75,8 @@ export function getReadToolSearchText(context: ToolSearchTextContext): string[] const { input, metadata } = readToolStatePayload(context.toolState) appendBaseToolText(values, context) appendString(values, input.filePath) + appendString(values, input.path) + appendString(values, input.name) appendString(values, metadata.preview) appendToolErrorText(values, context) return values diff --git a/packages/ui/src/components/tool-call/tool-title.ts b/packages/ui/src/components/tool-call/tool-title.ts index 0a6637a3..85a7f931 100644 --- a/packages/ui/src/components/tool-call/tool-title.ts +++ b/packages/ui/src/components/tool-call/tool-title.ts @@ -17,6 +17,7 @@ import { searchRenderer } from "./renderers/search" const TITLE_RENDERERS: Record = { bash: bashRenderer, read: readRenderer, + skill: readRenderer, write: writeRenderer, edit: editRenderer, apply_patch: applyPatchRenderer,