Closes the ai-chat code-review follow-ups.
Tests (security-critical paths previously uncovered):
- secret-box.spec: AES-256-GCM round-trip, random-salt uniqueness, tampered
blob / wrong APP_SECRET throw the expected message.
- ssrf-guard.spec: isIpAllowed blocks loopback/link-local/private/CGNAT/ULA/
unspecified/IPv4-mapped, allows public; isUrlAllowed blocks bad scheme,
invalid URL, IP literals, and DNS-rebinding (mocked dns.lookup).
- ai-chat.service.spec: assistantParts emits output-error for an UNPAIRED
tool call (guards the MissingToolResultsError fix), output-available when
paired, skips malformed calls; serializeSteps/rowToUiMessage.
- ai-chat-tools.service.spec: JSON-string node/content coercion + invalid-JSON
throws; updatePageJson title-only vs object.
- page-embedding.repo.spec: empty spaceIds early-returns [] with no DB call.
a11y:
- Chat history toggle and conversation rows are now keyboard-operable
(role=button, tabIndex, Enter/Space), matching history-item.tsx.
Refactors:
- onError on useChat adopts the server chat id when the first turn errors
(AI SDK v6 onFinish doesn't fire on error).
- isToolPart exported once from tool-parts and shared (was duplicated).
- buildInitialValues() dedups the ai-mcp-server-form initial values.
- describeProviderError replaces two inline statusCode/message snippets.
- tool-parts stale tool-list comment refreshed.
Implements docs/backlog/ai-chat-review-followups.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>