React error #31 (Objects are not valid as a React child) was thrown
intermittently when a task/subagent tool returned structured data
(e.g. { TODO: '...' }) in a field that the OpenCode SDK types as a
plain string. Pathological payloads would propagate into JSX children
without runtime validation, white-screening the chat until refresh.
This change adds a single `coerceToText` helper in toolRenderers.tsx
and applies it at every vulnerable JSX expression:
- ToolPart.tsx:1807,1975 {state.error} (typed string, can be object)
- ToolPart.tsx:1825 {q.question} (QuestionCard input cast)
- ToolPart.tsx:1830 {opt.label} (QuestionCard input cast)
- ToolPart.tsx:1848 task tool markdown output
- ToolPart.tsx:1898 ToolScrollableTextOutput entry
- toolRenderers.tsx {todo.content} x4 in renderTodoOutput
renderTodoOutput now also validates the parsed array at the boundary
(JSON.parse result is filtered to objects whose content and status are
runtime strings), so a single bad row no longer poisons the whole
tool output.
Tests: 12 new unit tests in
packages/ui/src/components/chat/message/parts/__tests__/issue-2011-react-error-31.test.ts
covering the {TODO}-key object path, circular references, and
non-string content/status on parsed todos.
Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>