SLOPSHOPPER

tool-cards

Replaces tool-use rows with diagnostic cards that show status, timing, results, and details

rowsguardtimer
★ 6v0.2.10MITupdated 2026-10-20bradtraversy/traversy-claude-mods/tool-cards
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · tool-cards
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ✎ Edit src/auth.ts Completed · Edit ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Tool row
✎ Edit src/auth.ts Completed · Edit
README

Tool Cards

Tool Cards replaces Claude Code's standard tool-use row with a clean, spacious card that makes the action, current state, tool, timing, and result easy to scan. Each card uses a tool-specific icon, a human-readable action title, muted metadata, and a right-aligned Details control when output is available. Tool output stays collapsed by default, including structured results from MCP tools.

VS Code uses one full-width header row with light padding and no icon tile. Output opens beneath it. The extension adds its own plugin attribution and outer frame; the mod does not control them. Desktop uses the full padded card treatment. The terminal uses a compact two-line row with no icon tile, outer margin, or vertical card padding so repeated tool calls do not take over the transcript.

Tool Cards showing a completed Bash command

Install

Add the marketplace once, then install the mod:

claude plugin marketplace add bradtraversy/traversy-claude-mods
claude plugin install tool-cards@traversy-mods

Restart the session or enter /reload-plugins after installation. Run /plugin to confirm it is enabled.

States

  • Running
  • Completed
  • Failed
  • Stopped

Each tool family has its own compact marker, so Bash, file operations, searches, agents, and MCP tools are easy to distinguish without relying on color alone. Running tools show live elapsed time. Completed tools keep their final duration when Tool Cards observed the call from its start.

The icon set uses >_ for Bash, ▤ for reads, ✎ for edits, + for writes, ⌕ for searches, ↗ for web calls, ◎ for agents, and ◆ for MCP tools.

Claude Code normally collapses consecutive reads, searches, and listings into a summary line. Tool Cards keeps those groups expanded so completed cards remain visible in the transcript.

Result details

Tool Cards adds a concise result line when the tool returns structured information:

  • Reads show how many lines were returned.
  • Edits and writes show added and removed line counts when Claude Code provides a Git diff.
  • Bash commands show output line counts, background task ids, or the number of changed files.
  • Completed agents show token and tool-call totals.

Bash output and edit patches have a right-aligned Details control. It changes to Hide details while the output is open. Failed calls open their details automatically so the error is visible immediately. Large output is capped to keep the transcript responsive, and oversized diffs report that they cannot be displayed instead of rendering a partial hunk.

For file tools, the card displays the file path. For Bash, it displays the command. It also handles tools that expose a path or pattern field.

Test Tool Cards

  1. Install and load tool-cards.
  2. Ask Claude to read a file.
  3. Watch the card change from Running to Completed and retain its elapsed time.
  4. Ask Claude to edit a tracked file and confirm that its added and removed line counts appear.
  5. Open Details and confirm that the highlighted diff appears inside the card.
  6. Ask Claude to run a command that fails and confirm that its output opens automatically in the Failed card.
  7. Interrupt a running command and confirm that Stopped uses the muted inactive color rather than the green success color.
  8. Run several reads or searches and confirm that their completed cards remain visible instead of collapsing into a summary line.

The green Completed label is part of this mod. Disable Tool Cards from /plugin to return to Claude Code's normal tool rows.

Uninstall

claude plugin uninstall tool-cards@traversy-mods

Source

hooks/register.tsx

Back to the mod index

Source 1 files
hooks/register.tsx 598 lines
1import type { EngineInterface, Register, Timer } from 'claude-code'
2
3const MAX_DETAIL_CHARS = 6_000
4const MAX_TRACKED_TOOLS = 200
5
6type ToolState = 'running' | 'done' | 'failed' | 'stopped'
7
8type ToolTiming = {
9  startedAt: number
10  endedAt?: number
11}
12
13type ToolDetail = {
14  source: string
15  format: 'source' | 'diff'
16  path?: string
17}
18
19const timings = new Map<string, ToolTiming>()
20const expansionOverrides = new Map<string, boolean>()
21let currentTime = 0
22let activeTools = 0
23let sessionGeneration = 0
24let stopTicker: Timer | null = null
25
26function isRecord(value: unknown): value is Record<string, unknown> {
27  return typeof value === 'object' && value !== null
28}
29
30function asNumber(value: unknown): number | undefined {
31  return typeof value === 'number' && Number.isFinite(value) ? value : undefined
32}
33
34function asString(value: unknown): string | undefined {
35  return typeof value === 'string' ? value : undefined
36}
37
38function truncate(value: string, maxLength: number): string {
39  if (value.length <= maxLength) return value
40  return `${value.slice(0, Math.max(0, maxLength - 3))}...`
41}
42
43function clearSessionState(): void {
44  sessionGeneration += 1
45  timings.clear()
46  expansionOverrides.clear()
47  currentTime = 0
48  activeTools = 0
49}
50
51function stopClockTicker(): void {
52  stopTicker?.cancel()
53  stopTicker = null
54}
55
56function rememberTiming(toolUseId: string, timing: ToolTiming): void {
57  timings.delete(toolUseId)
58  timings.set(toolUseId, timing)
59
60  if (timings.size > MAX_TRACKED_TOOLS) {
61    const oldest = timings.keys().next().value
62    if (oldest !== undefined) {
63      timings.delete(oldest)
64      expansionOverrides.delete(oldest)
65    }
66  }
67}
68
69async function readClock($: EngineInterface): Promise<number | undefined> {
70  try {
71    return await $.clock.now()
72  } catch {
73    return undefined
74  }
75}
76
77function startClockTicker($: EngineInterface): void {
78  stopClockTicker()
79  stopTicker = $.clock.every(1_000, () => {
80    if (activeTools === 0) return
81
82    void readClock($).then(now => {
83      if (now === undefined || activeTools === 0) return
84      currentTime = now
85      $.ui.invalidate('ui.render')
86    })
87  })
88}
89
90function compactPath(value: string): string {
91  const parts = value.replaceAll('\\', '/').split('/').filter(Boolean)
92  return parts.length > 1 ? parts.slice(-2).join('/') : value
93}
94
95function quoted(value: string): string {
96  return `"${truncate(value, 56)}"`
97}
98
99function actionTitle(tool: string, input: unknown): string {
100  if (!isRecord(input)) return `${toolLabel(tool)} call`
101
102  const description = asString(input.description)
103  if (description) return truncate(description, 78)
104
105  const subject = asString(input.subject)
106  if (tool.endsWith('__task_create') && subject) return truncate(subject, 78)
107  if (tool.endsWith('__task_update')) return 'Update Work Queue task'
108  if (tool.endsWith('__task_list')) return 'Review Work Queue'
109  if (tool.endsWith('__task_clear')) return 'Clear Work Queue'
110
111  const filePath = asString(input.file_path) ?? asString(input.path)
112  const target = filePath ? compactPath(filePath) : undefined
113
114  if (tool === 'Bash') return 'Run terminal command'
115  if (tool === 'Read') return target ? `Read ${target}` : 'Read file'
116  if (tool === 'Edit' || tool === 'NotebookEdit') return target ? `Edit ${target}` : 'Edit file'
117  if (tool === 'Write') return target ? `Write ${target}` : 'Write file'
118
119  const pattern = asString(input.pattern)
120  if (tool === 'Glob') return pattern ? `Find files matching ${quoted(pattern)}` : 'Find files'
121  if (tool === 'Grep') return pattern ? `Search for ${quoted(pattern)}` : 'Search project'
122
123  const query = asString(input.query)
124  if (tool === 'WebSearch') return query ? `Search the web for ${quoted(query)}` : 'Search the web'
125
126  const url = asString(input.url)
127  if (tool === 'WebFetch') return url ? `Open ${truncate(url, 64)}` : 'Open web page'
128
129  if (tool === 'Agent') return subject ? truncate(subject, 78) : 'Run agent task'
130
131  return `${toolLabel(tool)} call`
132}
133
134function status(isRunning: boolean, isErrored: boolean, isInterrupted: boolean): ToolState {
135  if (isInterrupted) return 'stopped'
136  if (isErrored) return 'failed'
137  if (isRunning) return 'running'
138  return 'done'
139}
140
141function statusColor(state: ToolState): 'success' | 'warning' | 'error' | 'inactive' {
142  if (state === 'failed') return 'error'
143  if (state === 'running') return 'warning'
144  if (state === 'stopped') return 'inactive'
145  return 'success'
146}
147
148function statusLabel(state: ToolState): string {
149  if (state === 'running') return 'Running'
150  if (state === 'done') return 'Completed'
151  if (state === 'failed') return 'Failed'
152  return 'Stopped'
153}
154
155function toolGlyph(tool: string): string {
156  if (tool === 'Bash') return '>_'
157  if (tool === 'Read') return '▤'
158  if (tool === 'Edit' || tool === 'NotebookEdit') return '✎'
159  if (tool === 'Write') return '+'
160  if (tool === 'Glob' || tool === 'Grep') return '⌕'
161  if (tool === 'WebSearch' || tool === 'WebFetch') return '↗'
162  if (tool === 'Agent') return '◎'
163  if (tool.startsWith('mcp__')) return '◆'
164  return '•'
165}
166
167function toolColor(tool: string): string {
168  if (tool === 'Bash') return 'claude'
169  if (tool === 'Read') return 'ide'
170  if (tool === 'Edit' || tool === 'NotebookEdit') return 'diffAdded'
171  if (tool === 'Write') return 'success'
172  if (tool === 'Glob' || tool === 'Grep') return 'warning'
173  if (tool === 'WebSearch' || tool === 'WebFetch') return 'ide'
174  if (tool === 'Agent') return 'remember'
175  if (tool.startsWith('mcp__')) return 'permission'
176  return 'subtle'
177}
178
179function toolLabel(tool: string): string {
180  if (!tool.startsWith('mcp__')) return truncate(tool, 24)
181
182  const [, server = 'mcp', ...nameParts] = tool.split('__')
183  const name = nameParts.join(' ').replaceAll('_', ' ')
184  return truncate(`${server}: ${name || 'tool'}`, 24)
185}
186
187function lineCount(value: string): number {
188  const normalized = value.trimEnd()
189  if (!normalized) return 0
190  return normalized.split('\n').length
191}
192
193function formatCount(value: number): string {
194  if (value < 1_000) return String(value)
195  if (value < 1_000_000) return `${Math.round(value / 100) / 10}k`
196  return `${Math.round(value / 100_000) / 10}m`
197}
198
199function formatDuration(durationMs: number): string {
200  if (durationMs < 1_000) return '<1s'
201  if (durationMs < 10_000) {
202    const seconds = Math.round(durationMs / 100) / 10
203    return `${seconds}s`
204  }
205  if (durationMs < 60_000) return `${Math.round(durationMs / 1_000)}s`
206
207  const minutes = Math.floor(durationMs / 60_000)
208  const seconds = Math.round((durationMs % 60_000) / 1_000)
209  return `${minutes}m ${String(seconds).padStart(2, '0')}s`
210}
211
212function timingLabel(toolUseId: string, output: unknown): string {
213  const timing = timings.get(toolUseId)
214  if (timing) {
215    const endedAt = timing.endedAt ?? currentTime
216    return formatDuration(Math.max(0, endedAt - timing.startedAt))
217  }
218
219  if (isRecord(output)) {
220    const duration = asNumber(output.totalDurationMs) ?? asNumber(output.durationMs)
221    if (duration !== undefined) return formatDuration(duration)
222  }
223
224  return ''
225}
226
227function diffSummary(output: Record<string, unknown>): string {
228  const gitDiff = isRecord(output.gitDiff) ? output.gitDiff : undefined
229  const additions = gitDiff ? asNumber(gitDiff.additions) : undefined
230  const deletions = gitDiff ? asNumber(gitDiff.deletions) : undefined
231
232  if (additions !== undefined && deletions !== undefined) {
233    return `+${additions} -${deletions}`
234  }
235
236  return ''
237}
238
239function bashFileCount(output: Record<string, unknown>): number {
240  const editDiff = isRecord(output.bashEditDiff) ? output.bashEditDiff : undefined
241  const files = editDiff && Array.isArray(editDiff.files) ? editDiff.files.length : 0
242  const moreFiles = editDiff ? asNumber(editDiff.moreFiles) ?? 0 : 0
243  return files + moreFiles
244}
245
246function resultSummary(tool: string, output: unknown, state: ToolState): string {
247  if (state === 'running') return ''
248  if (state === 'stopped') return 'Interrupted'
249  if (!isRecord(output)) return state === 'failed' ? 'Open details for the error' : ''
250
251  if (tool === 'Edit' || tool === 'Write' || tool === 'NotebookEdit') {
252    if (output.staged === true) return 'Staged for review'
253    const summary = diffSummary(output)
254    if (summary) return summary
255    if (tool === 'Write' && (output.type === 'create' || output.type === 'update')) {
256      return output.type === 'create' ? 'File created' : 'File updated'
257    }
258  }
259
260  if (tool === 'Read') {
261    const file = isRecord(output.file) ? output.file : undefined
262    const lines = file ? asNumber(file.numLines) : undefined
263    const total = file ? asNumber(file.totalLines) : undefined
264    if (lines !== undefined && total !== undefined) return `${lines} of ${total} lines`
265
266    const dimensions = file && isRecord(file.dimensions) ? file.dimensions : undefined
267    const width = dimensions ? asNumber(dimensions.originalWidth) : undefined
268    const height = dimensions ? asNumber(dimensions.originalHeight) : undefined
269    if (width !== undefined && height !== undefined) return `${width} x ${height}`
270  }
271
272  if (tool === 'Bash') {
273    const backgroundTaskId = asString(output.backgroundTaskId)
274    if (backgroundTaskId) return `Background task ${backgroundTaskId}`
275
276    const changedFiles = bashFileCount(output)
277    if (changedFiles > 0) return `${changedFiles} file${changedFiles === 1 ? '' : 's'} changed`
278
279    const stdout = asString(output.stdout) ?? ''
280    const stderr = asString(output.stderr) ?? ''
281    const outputLines = lineCount(stdout) + lineCount(stderr)
282    if (outputLines > 0) return `${outputLines} output line${outputLines === 1 ? '' : 's'}`
283    if (output.noOutputExpected === true) return 'No output expected'
284  }
285
286  if (tool === 'Agent' && output.status === 'completed') {
287    const parts: string[] = []
288    const tokens = asNumber(output.totalTokens)
289    const tools = asNumber(output.totalToolUseCount)
290    if (tokens !== undefined) parts.push(`${formatCount(tokens)} tokens`)
291    if (tools !== undefined) parts.push(`${tools} tool${tools === 1 ? '' : 's'}`)
292    return parts.join(' | ')
293  }
294
295  return state === 'failed' ? 'Open details for the error' : ''
296}
297
298function outputDetail(tool: string, output: unknown): ToolDetail | null {
299  if (typeof output === 'string' && output) {
300    try {
301      const parsed = JSON.parse(output)
302      if (isRecord(parsed) || Array.isArray(parsed)) {
303        return {
304          source: truncate(JSON.stringify(parsed, null, 2), MAX_DETAIL_CHARS),
305          format: 'source',
306        }
307      }
308    } catch {
309      // Plain-text output stays plain text.
310    }
311
312    return {
313      source: truncate(output, MAX_DETAIL_CHARS),
314      format: 'source',
315    }
316  }
317
318  if (!isRecord(output)) return null
319
320  if (tool === 'Edit' || tool === 'Write' || tool === 'NotebookEdit') {
321    const gitDiff = isRecord(output.gitDiff) ? output.gitDiff : undefined
322    const patch = gitDiff ? asString(gitDiff.patch) : undefined
323    if (patch) {
324      if (patch.length > MAX_DETAIL_CHARS) {
325        return {
326          source: `Diff is too large to display (${Math.round(patch.length / 1_000)}k characters).`,
327          format: 'source',
328        }
329      }
330
331      return {
332        source: patch,
333        format: 'diff',
334        path: gitDiff ? asString(gitDiff.filename) : undefined,
335      }
336    }
337  }
338
339  if (tool === 'Bash') {
340    const stdout = asString(output.stdout) ?? ''
341    const stderr = asString(output.stderr) ?? ''
342    const sections = [
343      stdout ? stdout.trimEnd() : '',
344      stderr ? `stderr:\n${stderr.trimEnd()}` : '',
345    ].filter(Boolean)
346
347    if (sections.length > 0) {
348      return {
349        source: truncate(sections.join('\n\n'), MAX_DETAIL_CHARS),
350        format: 'source',
351      }
352    }
353  }
354
355  if (typeof output.text === 'string' && output.text) {
356    return {
357      source: truncate(output.text, MAX_DETAIL_CHARS),
358      format: 'source',
359    }
360  }
361
362  const serialized = JSON.stringify(output, null, 2)
363  if (serialized) {
364    return {
365      source: truncate(serialized, MAX_DETAIL_CHARS),
366      format: 'source',
367    }
368  }
369
370  return null
371}
372
373export const register: Register = on => {
374  on('session.start', async ($, event, next) => {
375    stopClockTicker()
376    clearSessionState()
377    currentTime = await readClock($) ?? 0
378    startClockTicker($)
379    return next(event)
380  })
381
382  on('session.end', ($, event, next) => {
383    clearSessionState()
384    if (event.reason !== 'clear' && event.reason !== 'resume') stopClockTicker()
385    $.ui.invalidate('ui.render')
386    return next(event)
387  })
388
389  on('tool.call', async ($, event, next) => {
390    const generation = sessionGeneration
391    const startedAt = await readClock($)
392
393    if (startedAt !== undefined && generation === sessionGeneration) {
394      currentTime = startedAt
395      rememberTiming(event.tool_use_id, { startedAt })
396      activeTools += 1
397      $.ui.invalidate('ui.render')
398    }
399
400    try {
401      return await next(event)
402    } finally {
403      if (generation === sessionGeneration && startedAt !== undefined) {
404        const endedAt = await readClock($)
405        if (generation === sessionGeneration) {
406          activeTools = Math.max(0, activeTools - 1)
407          if (endedAt !== undefined) {
408            currentTime = endedAt
409            rememberTiming(event.tool_use_id, { startedAt, endedAt })
410            $.ui.invalidate('ui.render')
411          } else {
412            timings.delete(event.tool_use_id)
413          }
414        }
415      }
416    }
417  })
418
419  on('ui.render', { component: 'ToolGroup' }, ($, event, next) => {
420    if (event.props.isExpanded) return next(event)
421
422    return next({
423      ...event,
424      props: {
425        ...event.props,
426        isExpanded: true,
427      },
428    })
429  })
430
431  on('ui.render', { component: 'ToolUse' }, ($, event) => {
432    const { Box, Button, Code, Text } = $.ui.resolve(event)
433    const state = status(event.props.isRunning, event.props.isErrored, event.props.isInterrupted)
434    const color = statusColor(state)
435    const summary = resultSummary(event.props.tool, event.props.output, state)
436    const duration = timingLabel(event.props.tool_use_id, event.props.output)
437    const detail = outputDetail(event.props.tool, event.props.output)
438    const expanded = expansionOverrides.get(event.props.tool_use_id) ?? event.props.isErrored
439    const toggleDetails = (): void => {
440      expansionOverrides.set(event.props.tool_use_id, !expanded)
441      $.ui.invalidate('ui.render')
442    }
443
444    if (event.surface === 'vscode') {
445      return (
446        <Box flexDirection="column" minWidth={0} overflow="hidden" paddingX={0.5} paddingY={0.25}>
447          <Box alignItems="center" gap={1} overflow="hidden">
448            <Text color={toolColor(event.props.tool)} bold>{toolGlyph(event.props.tool)}</Text>
449            <Box flexGrow={1} flexShrink={1} minWidth={0} overflow="hidden">
450              <Text bold wrap="truncate-middle">{actionTitle(event.props.tool, event.props.input)}</Text>
451            </Box>
452            <Text color={color}>{statusLabel(state)}</Text>
453            <Text dimColor>{toolLabel(event.props.tool)}</Text>
454            {duration && <Text dimColor>{duration}</Text>}
455            {summary && <Text dimColor wrap="truncate-middle">{summary}</Text>}
456            {detail && (
457              <Box flexShrink={0}>
458                <Button
459                  key={`tool-card-details:${event.props.tool_use_id}`}
460                  label={expanded ? 'Hide details' : 'Details'}
461                  variant="secondary"
462                  onPress={toggleDetails}
463                />
464              </Box>
465            )}
466          </Box>
467          {detail && expanded && (
468            <Code source={detail.source} format={detail.format} path={detail.path} wrap="truncate-end" />
469          )}
470        </Box>
471      )
472    }
473
474    if (event.surface === 'terminal') {
475      return (
476        <Box flexDirection="column" overflow="hidden">
477          <Box justifyContent="space-between" alignItems="center" gap={1} overflow="hidden">
478            <Box gap={1} flexGrow={1} flexShrink={1} minWidth={0} overflow="hidden">
479              <Text color={toolColor(event.props.tool)} bold>{toolGlyph(event.props.tool)}</Text>
480              <Text bold wrap="truncate-middle">
481                {actionTitle(event.props.tool, event.props.input)}
482              </Text>
483            </Box>
484            {detail && (
485              <Box flexShrink={0}>
486                <Button
487                  key={`tool-card-details:${event.props.tool_use_id}`}
488                  label={expanded ? 'Hide details' : 'Details'}
489                  variant="secondary"
490                  onPress={toggleDetails}
491                />
492              </Box>
493            )}
494          </Box>
495          <Box marginLeft={3} gap={1} alignItems="center" flexWrap="wrap" overflow="hidden">
496            <Text color={color} bold>{statusLabel(state)}</Text>
497            <Text dimColor>·</Text>
498            <Text dimColor>{toolLabel(event.props.tool)}</Text>
499            {duration && <Text dimColor>·</Text>}
500            {duration && <Text dimColor>{duration}</Text>}
501            {summary && <Text dimColor>·</Text>}
502            {summary && <Text dimColor wrap="truncate-middle">{summary}</Text>}
503          </Box>
504          {detail && expanded && (
505            <Box marginLeft={3} marginTop={1}>
506              <Code
507                source={detail.source}
508                format={detail.format}
509                path={detail.path}
510                wrap="truncate-end"
511              />
512            </Box>
513          )}
514        </Box>
515      )
516    }
517
518    return (
519      <Box
520        borderStyle="round"
521        borderDimColor
522        paddingX={2}
523        paddingY={1}
524        marginY={1}
525        flexDirection="column"
526        overflow="hidden"
527        gap={1}
528      >
529        <Box justifyContent="space-between" alignItems="center" gap={2} overflow="hidden">
530          <Box
531            gap={2}
532            alignItems="center"
533            flexGrow={1}
534            flexShrink={1}
535            minWidth={0}
536            overflow="hidden"
537          >
538            <Box
539              borderStyle="round"
540              borderColor={toolColor(event.props.tool)}
541              backgroundColor={toolColor(event.props.tool)}
542              paddingX={1}
543              flexShrink={0}
544            >
545              <Text color="inverseText" bold>{toolGlyph(event.props.tool)}</Text>
546            </Box>
547            <Box
548              flexDirection="column"
549              flexGrow={1}
550              flexShrink={1}
551              minWidth={0}
552              overflow="hidden"
553            >
554              <Text bold wrap="truncate-middle">
555                {actionTitle(event.props.tool, event.props.input)}
556              </Text>
557              <Box gap={1} alignItems="center" flexWrap="wrap" overflow="hidden">
558                <Text color={color} bold>{statusLabel(state)}</Text>
559                <Text dimColor>·</Text>
560                <Text dimColor>{toolLabel(event.props.tool)}</Text>
561                {duration && <Text dimColor>·</Text>}
562                {duration && <Text dimColor>{duration}</Text>}
563                {summary && <Text dimColor>·</Text>}
564                {summary && <Text dimColor wrap="truncate-middle">{summary}</Text>}
565              </Box>
566            </Box>
567          </Box>
568          {detail && (
569            <Box flexShrink={0}>
570              <Button
571                key={`tool-card-details:${event.props.tool_use_id}`}
572                label={expanded ? 'Hide details' : 'Details'}
573                variant="secondary"
574                onPress={toggleDetails}
575              />
576            </Box>
577          )}
578        </Box>
579        {detail && expanded && (
580          <Box marginLeft={7} paddingX={1} paddingY={1} borderStyle="round" borderDimColor>
581            <Code
582              source={detail.source}
583              format={detail.format}
584              path={detail.path}
585              wrap="truncate-end"
586            />
587          </Box>
588        )}
589      </Box>
590    )
591  })
592
593  on('ui.render', { component: 'ToolResult' }, ($, event) => {
594    const { Box } = $.ui.resolve(event)
595    return <Box />
596  })
597}
598