[UI-REWRITE] Render tool preview results - #55
Conversation
Signed-off-by: Pratik Gandhi <gandhipratik203@gmail.com>
e4388d8 to
83d2190
Compare
marekdano
left a comment
There was a problem hiding this comment.
Findings
High — silently breaks the feature
1. Stale expanded state leaks across Preview re-runs
src/components/tools/ToolResultRenderer.tsx:91
ToolResultBlock's expanded state (useState(!block.isLarge)) only initializes on mount, but blocks are keyed by ${block.type}-${block.mimeType}-${index} (line 54), and useToolPreview.run() never clears the previous result before a new one resolves — so the component never remounts between two Preview runs on the same tool.
Failure scenario: user expands a large block, then re-runs Preview with different args; if the new response's block at the same index has the same type/mimeType, it inherits the stale expanded=true and renders fully open immediately — silently bypassing the PR's own "large result protection" feature.
2. SVG images delivered via text render as raw markup
src/components/tools/ToolResultRenderer.tsx:145
isTextualMime (matches any mimetype containing "xml") is checked before the image/-prefix branch, so an image delivered via text (not base64 data) with mimeType: "image/svg+xml" renders as raw markup in a CodeBlock instead of an <img>. The PR's own manual-test script sidesteps this by always base64-encoding its SVG into data.
Medium — real correctness bugs, narrower blast radius
3. Duplicated ToolCodeLanguage type will mislabel content after rebase
src/components/tools/toolResultContent.ts:18,56
ToolCodeLanguage/codeLanguageForMime redefine a narrower duplicate of CodeBlockLanguage ("bash"|"json"|"tsx" vs. the real "bash"|"json"|"python"|"tsx"|"markdown"|"xml"|"text" already on origin/main), so XML content gets highlighted with the TSX grammar and plain text with the bash grammar once this branch merges, despite proper grammars already existing. (code-block.tsx isn't touched by this PR's diff — origin/main already gained markdown/xml/text support from an earlier merged PR; this branch just hasn't rebased onto that yet, so the type should reuse CodeBlockLanguage directly rather than redefining a narrower copy.)
4. Byte size inflated ~33% for binary/image blocks
src/components/tools/toolResultContent.ts:164
getBlockByteSize/getStringByteSize measures the byte length of the base64-encoded data string itself, not the decoded binary size, inflating displayed size and the isLarge (256KB) comparison by ~33% for every binary/image block.
5. Empty-string data mishandled
src/components/tools/toolResultContent.ts:79
getDataUrl uses a truthy check (if (block.data)) instead of !== undefined, so a block with explicit empty-string data: "" falls through to a confusing generic JSON-dump fallback instead of a valid empty data URL.
Low-medium — regressions / polish
6. Raw response now collapsed by default
src/components/tools/ToolPreviewResult.tsx
The raw preview response used to be an always-visible <section>; it's now a collapsible Accordion with no defaultValue, so it's hidden by default — inconsistent with the structured-output Accordion a few lines away in ToolResultRenderer.tsx, which explicitly sets defaultValue="structured-output" to stay open. Regresses the "still available for debugging" workflow the PR description claims to preserve.
7. Hardcoded English fallback string breaks i18n
src/components/tools/ToolPreviewResult.tsx:158
formatWarningHooks falls back to the hardcoded English literal "one or more hooks" instead of an i18n key, producing mixed-language warning text for es-ES/pt-BR users when a warning has no hook/hooks field.
vishu-bh
left a comment
There was a problem hiding this comment.
Thanks @gandhipratik203 for the changes.
It is well defined and approach is headed in right direction.
Please check these inline findings:
|
|
||
| {blocks.map((block, index) => ( | ||
| <ToolResultBlock | ||
| key={`${block.type}-${block.mimeType}-${index}`} |
There was a problem hiding this comment.
Block key only uses type, MIME, and index, so React reuses expanded state across preview reruns. A small result initializes expanded=true; a later huge same-slot result bypasses collapse and reaches Prism immediately. Include invocation/result identity in the key and add rerender regression tests.
| /> | ||
| ))} | ||
|
|
||
| {hasStructuredOutput && ( |
There was a problem hiding this comment.
Per-block limit does not protect aggregate content or structured_output. Many sub-limit blocks, or one huge structured output, are eagerly serialized/highlighted and can freeze the tab. Add total-byte and block-count limits, cap structured output, and serialize only after explicit expansion.
| import { ToolResultRenderer } from "./ToolResultRenderer"; | ||
| import { TOOL_RESULT_BLOCK_SIZE_LIMIT_BYTES } from "./toolResultContent"; | ||
|
|
||
| describe("ToolResultRenderer", () => { |
There was a problem hiding this comment.
Issue verification requires MIME-focused tests plus Playwright JSON, text, image, error, large-block, warning, and empty-result cases. Current suite omits most paths and CI fails the global branch threshold. Add positive, negative, boundary, and rerun cases before closing the issue.
| data?: string; | ||
| raw: ToolResultContentBlock | string; | ||
| }): number { | ||
| if (input.text !== undefined) return getStringByteSize(input.text); |
There was a problem hiding this comment.
data/blob fields contain base64, but Blob([input.data]) counts encoded characters. This produces wrong captions and thresholds. Decode validated base64—or use trusted response metadata—before computing binary size.
Summary
In simple terms: when you click Preview for a tool, PR 1 (#53) showed mostly raw JSON. This PR makes the result easier to read.
It adds UI support for:
Before / After
Context
Notes
VITE_ENABLE_TOOL_PREVIEWinherited from PR 1.Tests
npm run testnpx tsc --noEmit -p tsconfig.app.jsonnpm run e2e -- e2e/tools.spec.tsnpm run format:checknpm run lintgit diff --checkManual verification
Manual test steps
Setup
Save the mock script from the next collapsible at the repo root as
tool-result-rendering-manual.mjs.Two terminals:
Terminal B opens a Chrome for Testing window with
/auth/session,/api/rbac/my/permissions,/api/tools,/api/gateways, and two/api/tools/preview/*responses mocked. Ctrl-C in terminal B to close. Do everything in that window, in the tab it opens.Steps
1. Open More options for render-lab -> View details.
Expect: the details drawer opens with Try it selected.
2. Confirm both tool chips are visible:
render_rich_resultandrender_error_large_result.3. For
render_rich_result, fillquerywithcloudflareandlimitwith5, then click Preview.Expect: Preview 200, Warnings, Tool result, Content block 1..5, Structured output, and Raw preview response.
4. Inspect the rich result.
Expect: text output, formatted JSON with
"total": 2, an inline image, PDF Open in new tab + Download raw, binary Download raw, and warning text forapproval_hookplus the mocked server default.5. Click the
render_error_large_resulttool chip.6. Fill
querywithfailure, then click Preview.Expect: Preview 200 with an Error response badge.
7. Inspect the large content block.
Expect: Large content hidden (...) and a View all button.
8. Click View all.
Expect:
END_OF_LARGE_RESULTappears.Teardown
Ctrl-C both terminals. If :5173 is stuck:
Mock script (tool-result-rendering-manual.mjs)
Save at the repo root. Requires
@playwright/test, already a dev dependency; runnpx playwright install chromiumif the browser is missing.Manual test results
Run against
feat/6317-tool-result-renderingat83d2190, rebased onmainatff32f35.render-labrender_rich_resultandrender_error_large_resultare visiblerender_rich_resultrender_error_large_resultEND_OF_LARGE_RESULTappearsObserved terminal output includes
tools card: okand preview request logs for both mocked tools.Scope of this verification: all backend responses are mocked. This covers frontend result rendering only: text, JSON, image, PDF/download actions, structured output, warning messages, error-result badges, and large-content collapse behavior. It does not verify a live backend preview endpoint.