feat(ui): render skill tool output as markdown

Split skill handling out of the read tool renderer so skill output can render completed state.output as markdown instead of being forced into read-style preview code blocks.

The skill renderer stays collapsed by default through the existing skill expansion rule, preserves copy support for the rendered output, and uses scoped tokenized padding while inheriting the standard compact tool output height.

Read tool rendering is returned to read-only preview behavior, and title/renderer lookup now resolve skill through its dedicated renderer.

Validated with npm run typecheck --workspace @codenomad/ui.
This commit is contained in:
Shantur Rathore 2026-06-18 12:35:48 +01:00
parent 0300863a34
commit 311ea4273e
6 changed files with 37 additions and 2 deletions

View file

@ -5,6 +5,7 @@ import { editRenderer } from "./edit"
import { applyPatchRenderer } from "./apply-patch"
import { patchRenderer } from "./patch"
import { readRenderer } from "./read"
import { skillRenderer } from "./skill"
import { taskRenderer } from "./task"
import { todoRenderer } from "./todo"
import { webfetchRenderer } from "./webfetch"
@ -15,6 +16,7 @@ import { searchRenderer } from "./search"
const TOOL_RENDERERS: ToolRenderer[] = [
bashRenderer,
skillRenderer,
readRenderer,
writeRenderer,
editRenderer,

View file

@ -12,7 +12,7 @@ function getReadPath(input: Record<string, any>): string {
}
export const readRenderer: ToolRenderer = {
tools: ["read", "skill"],
tools: ["read"],
getSearchText: getReadToolSearchText,
getAction: () => tGlobal("toolCall.renderer.action.readingFile"),
getTitle({ toolName, toolState }) {

View file

@ -0,0 +1,28 @@
import type { ToolRenderer } from "../types"
import { ensureMarkdownContent, formatUnknown, getToolName } from "../utils"
import { getDefaultToolSearchText } from "../search-text"
export const skillRenderer: ToolRenderer = {
tools: ["skill"],
getSearchText: getDefaultToolSearchText,
getTitle() {
return getToolName("skill")
},
getOutputChrome({ toolState }) {
const state = toolState()
if (!state || state.status !== "completed") return undefined
const output = formatUnknown(state.output)?.text ?? null
if (!output) return undefined
return { copyText: output, suppressInnerHeader: true }
},
renderBody({ toolState, renderMarkdown }) {
const state = toolState()
if (!state || state.status !== "completed") return null
const output = formatUnknown(state.output)?.text ?? null
const content = ensureMarkdownContent(output, undefined, false)
if (!content) return null
return <div class="tool-call-skill-body">{renderMarkdown({ content })}</div>
},
}

View file

@ -5,6 +5,7 @@ import { enMessages } from "../../lib/i18n/messages/en"
import { defaultRenderer } from "./renderers/default"
import { bashRenderer } from "./renderers/bash"
import { readRenderer } from "./renderers/read"
import { skillRenderer } from "./renderers/skill"
import { writeRenderer } from "./renderers/write"
import { editRenderer } from "./renderers/edit"
import { applyPatchRenderer } from "./renderers/apply-patch"
@ -17,7 +18,7 @@ import { searchRenderer } from "./renderers/search"
const TITLE_RENDERERS: Record<string, ToolRenderer> = {
bash: bashRenderer,
read: readRenderer,
skill: readRenderer,
skill: skillRenderer,
write: writeRenderer,
edit: editRenderer,
apply_patch: applyPatchRenderer,

View file

@ -1,6 +1,7 @@
/* Tool call rendering */
@import "./tool-call/todo.css";
@import "./tool-call/task.css";
@import "./tool-call/skill.css";
.tool-call-message {
@apply flex flex-col gap-0 p-0 w-full;

View file

@ -0,0 +1,3 @@
.tool-call-skill-body {
padding: var(--space-md);
}