Render the current Claude Code tool surface in playback#45
Open
chigichan24 wants to merge 8 commits into
Open
Conversation
Adds dedicated renderers in ToolCallBlock for the modern Claude Code tool set so playback no longer collapses into raw JSON dumps. Per-tool helpers were extracted into ToolCallRenderers / toolCallHelpers to keep the main component readable. New / updated coverage (priority follows the corpus histogram emitted by scripts/aggregate-tool-names.ts): - High: TaskCreate, TaskUpdate, AskUserQuestion, Skill, WebFetch, WebSearch, NotebookEdit, MultiEdit (alias of Edit), Task (alias of Agent) - Mid: ExitPlanMode (with plan + allowed prompts), EnterPlanMode, EnterWorktree / ExitWorktree, Monitor, ScheduleWakeup, ToolSearch - Low: a single generic mcp__server__method formatter showing server, method, and 1–2 key args - Fallback: unknown tools now show 1–2 high-signal scalar args via pickKeyInputArgs instead of dumping the whole input as JSON Also adds: - scripts/aggregate-tool-names.ts: one-shot histogram of tool_use blocks across ~/.claude/projects/, used to derive the priority list - Pure helpers (getToolCategory, parseMcpToolName, summarizeValue, pickKeyInputArgs, truncate) with unit tests Closes #21 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous code cast q.options items to {label?: string; description?: string}
without runtime validation. Since the input arrives from JSONL data of unknown
shape, accessing opt.label / opt.description on a non-object item would throw.
Filter the array to objects up front and validate field types per option.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
input.command / input.spec may contain a structured object spec. Calling asString on it produced the unhelpful "[object Object]" rendering. Use a scalar-only guard so structured payloads fall through to the generic key/value renderer instead. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
q.header and q.question were typed as optional strings but never validated at runtime. A non-string header (e.g. null cleared via JSON, or an unexpected object) would either render incorrectly or, in the case of question, throw inside truncate() which calls String.prototype.slice. Coerce to null when the field is not a string before rendering. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
p.tool and p.prompt were rendered without runtime validation. A non-string prompt would cause truncate() to call slice() on a non-string and throw, and a non-string tool would render via React's default coercion. Coerce both to null when they aren't strings before rendering. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
asString previously fell back to String(value) for non-string values, which produced "[object Object]" for objects and "1,2,3" for arrays whenever the JSONL payload had an unexpected shape. Restrict it to strings, numbers, and booleans so structured payloads return null and downstream renderers can opt into a structured fallback (e.g. GenericInputRenderer) instead. Removes the local asScalarString helper that was added earlier for the same reason. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
allowed_domains / blocked_domains were rendered via map(String).join(', '),
which produces "[object Object]" for any non-string entry that slips through
the JSONL payload. Filter to non-empty strings so display stays clean and
sliced counts match what is actually shown.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The status string from JSONL was concatenated into a CSS class modifier without any validation. A status containing whitespace (e.g. "in progress") would split into two class tokens, dropping the intended modifier and adding a stray class. Restrict the modifier to safe characters and fall back to "unknown" otherwise. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ToolCallBlockfor the modern Claude Code tool set so playback no longer collapses into raw JSON dumps. Per-tool helpers are extracted intoToolCallRenderers/toolCallHelpersto keep the main component readable.TaskCreate,TaskUpdate,AskUserQuestion,Skill,WebFetch,WebSearch,NotebookEdit, plusMultiEditandTaskas aliases of the existingEditandAgentrenderers.ExitPlanMode(plan body + allowed prompts),EnterPlanMode,EnterWorktree/ExitWorktree,Monitor,ScheduleWakeup,ToolSearch.mcp__<server>__<method>formatter that shows server, method and 1–2 key args viapickKeyInputArgs.url,file_path,command,query, …) instead of dumping JSON.scripts/aggregate-tool-names.ts: one-shot histogram oftool_useblocks across~/.claude/projects/used to derive the priority list. Running it locally on this corpus reproduces the order the issue asked for (TaskUpdate / ToolSearch / WebFetch / WebSearch / ExitPlanMode / TaskCreate / AskUserQuestion at the top of the new-tool tail).ToolCallBlock.css.Notes / assumptions:
getToolCategory,parseMcpToolName,summarizeValue,pickKeyInputArgs,truncate) rather than rendered output. Visual review of the renderers is intentionally left to manual playback.MonitorandScheduleWakeupdid not appear in the local corpus often enough to firmly pin field shapes, so their renderers fall back toGenericInputRendererif none of the expected fields (command/spec,at/when/after, …) are present.EnterPlanModeinput is usually empty in the corpus, so the renderer prints a short status line in that case.MultiEditis currently rendered with theEditlayout. Switching to a list-of-edits view can be done in a follow-up once we see realMultiEditpayloads.Test plan
npm run lint(only pre-existingPlaybackSidePanelwarnings remain)npm test(18 files, 230 tests, including the newtoolCallHelperssuite)npx tsc --noEmit -p tsconfig.app.jsonnpx tsc --noEmit -p tsconfig.node.jsonnpx tsx scripts/aggregate-tool-names.ts --limit 25reproduces the issue's priority listCloses #21
🤖 Generated with Claude Code