fix(ui): normalize pending tool-call previews + pending renderer states (split 1 of #27) - #32
Conversation
…e keys Split out of PR #27 as the independently-mergeable UI slice, rebased on main. - Normalize tool-call args to {} before rendering previews so a chat:tool-call-start event (which fires before streamed args arrive) can't crash a preview renderer reading args.title / args.path / args.command etc. Centralized in registerPreviewRenderer + normalizeToolCallArgs, with safe getStringArg/getPathArg accessors in the preview renderers. - FileEdit/FileWrite renderers show explicit pending states while args stream. - Add the three previously-missing i18n keys these states use (tools.status.pending, tools.renderers.editingFile, tools.renderers.writingFile) to en.json and all nine other locales so the English defaultValue no longer leaks into every language. `bun scripts/check-locales.ts` passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… not failed CodeRabbit flagged MultiEditRenderer showing the red "failed" UI for an unresolved call (success is false while result is undefined). ListDirectoryRenderer had the identical latent bug. Both now distinguish pending from failed the same way FileEditRenderer/FileWriteRenderer do (failed = real error or completed-unsuccessful), and render a spinner + pending state while the call streams. Adds the `tools.renderers.listingDirectory` key across all 10 locales for the list_directory pending message; multi_edit reuses `editingFile`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR hardens the chat UI’s tool-call rendering to behave safely during the “pending/streaming args” window, preventing crashes in tool preview renderers and avoiding incorrect “failed” flashes for file-operation renderers. It also completes the corresponding i18n additions across all supported locales.
Changes:
- Normalize tool preview renderer
argsto{}duringchat:tool-call-start/ pending states, and add safer arg access in preview renderers. - Add explicit pending UI states (spinner + pending text) for file-operation result renderers (including MultiEdit and ListDirectory) so pending calls don’t appear failed.
- Add new i18n keys for pending/tool renderer messaging across all 10 locales, plus targeted tests for normalization and pending rendering behavior.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/server/tools/shell-tools.test.ts | Makes /tmp expectations robust by resolving symlinks via realpathSync. |
| src/client/locales/en.json | Adds new tool pending/renderer strings for English. |
| src/client/locales/de.json | Adds new tool pending/renderer strings for German. |
| src/client/locales/es.json | Adds new tool pending/renderer strings for Spanish. |
| src/client/locales/fr.json | Adds new tool pending/renderer strings for French. |
| src/client/locales/it.json | Adds new tool pending/renderer strings for Italian. |
| src/client/locales/ja.json | Adds new tool pending/renderer strings for Japanese. |
| src/client/locales/pl.json | Adds new tool pending/renderer strings for Polish. |
| src/client/locales/pt-BR.json | Adds new tool pending/renderer strings for Brazilian Portuguese. |
| src/client/locales/ru.json | Adds new tool pending/renderer strings for Russian. |
| src/client/locales/zh-CN.json | Adds new tool pending/renderer strings for Simplified Chinese. |
| src/client/lib/tool-registry.ts | Normalizes preview-renderer args to {} to avoid pending-state crashes. |
| src/client/lib/tool-preview-renderers.ts | Uses safer arg accessors for common preview renderers (path/command/sql/etc.). |
| src/client/lib/tool-preview-renderers.test.ts | Adds regression tests ensuring previews/renderers don’t throw or flash failed while pending. |
| src/client/components/chat/ToolCallItem.tsx | Normalizes args to {} before passing into preview/renderers/JSON viewer. |
| src/client/components/chat/renderers/FileEditRenderer.tsx | Adds explicit pending state UI and safer args access. |
| src/client/components/chat/renderers/FileReadRenderer.tsx | Makes args.path access null-safe for pending tool calls. |
| src/client/components/chat/renderers/FileWriteRenderer.tsx | Adds explicit pending state UI and safer args access. |
| src/client/components/chat/renderers/MultiEditRenderer.tsx | Distinguishes pending vs failed and renders pending spinner/message. |
| src/client/components/chat/renderers/ListDirectoryRenderer.tsx | Distinguishes pending vs failed and renders pending spinner/message. |
| src/client/components/chat/InlineToolCall.tsx | Introduces normalizeToolCallArgs and applies it for inline tool call rendering. |
| src/client/components/chat/InlineToolCall.test.ts | Adds tests for normalizeToolCallArgs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
MarlBurroW
left a comment
There was a problem hiding this comment.
Reviewed and verified, this is exactly the split we hoped for. Thank you @kdegeek!
What we checked before merging:
- Full diff read: the central normalization in
registerPreviewRendererplus thegetStringArg/getPathArgaccessors are clean, and thefailed = status === 'error' || error !== null || (result !== undefined && !success)distinction is sound (asuccesscall always carries its result, so no renderer can get stuck on the pending state). - The MultiEdit and ListDirectory additions are correct: both really did render the red failed UI for an in-flight call before this.
bun scripts/check-locales.ts,bun run typecheck, fullbun run test(4094 tests) all green on the branch.- Booted the built app and ran a live
write_file+list_directoryturn end to end: previews render through the new code path, both calls resolve to their success UI, zero console/page errors.
Also appreciated: real assertions in the new tests (exact preview strings, pending-not-failed markup) and the locale keys shipped to all 10 languages with parity passing. Merging. Looking forward to the next splits of #27.
Summary
First split out of #27 (the runtime/UI hardening branch), per @MarlBurroW's suggested breakdown — the self-contained, easily-mergeable UI slice, rebased on current
main.chat:tool-call-startrenders a preview before streamed tool args arrive, so a preview renderer readingargs.path/args.command/args.titlecould crash on a still-pending call. Args are now normalized to{}centrally inregisterPreviewRenderer(andnormalizeToolCallArgs), with safegetStringArg/getPathArgaccessors in the preview renderers.tools.status.pending,tools.renderers.editingFile,tools.renderers.writingFile— toen.jsonand all nine other locales, so the EnglishdefaultValueno longer leaks into every language.Two additions beyond the original branch (both in-theme)
successisfalsewhileresultisundefined). They now distinguish pending from failed exactly like FileEdit/FileWrite (failed = status === 'error' || error !== null || (result !== undefined && !success)). One caught by a CodeRabbit pass (MultiEdit), the other found alongside it (ListDirectory).tools.renderers.listingDirectory, for the ListDirectory pending message (multi_edit reuseseditingFile).Validation
bun run typecheck— PASSbun run test— PASS (3985 tests)bun run build— PASS (pre-existing Vite chunk-size warnings only)bun scripts/check-locales.ts— PASS (all 10 locales, matching key paths, no em-dashes)git diff --check— PASSThe remaining pieces of #27 (crash-safe pre-insert/checkpoint, the multi-candidate auth/cache path resolution +
getRealHomeboot-crash fix, and the restart-gating / toolbox-grant design questions) will follow as separate PRs / discussion.🤖 Generated with Claude Code