fix(conversations): show a pending spinner for unresolved tool nodes instead of "(empty)" - #12727
Merged
diegosouzapw merged 2 commits intoSep 10, 2026
Conversation
…instead of "(empty)" conversation_turn_nodes records a turn's identity (role, blockKind) the moment it's recorded, independent of when its display content resolves from the owning call-log artifact (resolveTurnDisplayContent). A tool node for a request still in flight is real -- it just has nothing to show yet -- but /api/conversations/[id]/tree's own blockKind ?? "text" fallback can't tell that apart from a permanently-purged node, so /dashboard/conversations rendered both as a bare "(empty)" bubble that read as broken rather than in progress. The frontend CAN tell them apart: role is set at record time and is always "tool" for a real tool identity node, regardless of content resolution. Map that combination (role==="tool" && !textPreview) to a new NormalizedBlock pending variant instead, rendered with a spinner; every other mapping is unchanged. Also extracted toTurn()/ConversationTurn out of the conversations page (a "use client" component that pulls in ChatBubble/MarkdownMessage's dependency tree) into a small colocated pure module, so this mapping logic is unit-testable without a browser/DOM harness. Net production LOC in page.tsx goes down; the new toTurn.ts is the same logic moved, plus the new pending branch.
hartmark
marked this pull request as ready for review
September 4, 2026 11:28
hartmark
added a commit
to hartmark/OmniRoute
that referenced
this pull request
Sep 4, 2026
…er for unresolved tool nodes instead of "(empty)") into dev/omniroute-dev-combined
…s, rename to "loading..." Live traffic showed the same textPreview resolution lag the earlier fix covered for tool nodes also hits user/assistant nodes -- they hit the same lazy resolveTurnDisplayContent pipeline. Drop the role==="tool" restriction so any node with no textPreview yet (in the plain-text fallback branch) renders the pending spinner, not just tool. Also renames the label from "resolving..." to "loading..." per feedback.
hartmark
added a commit
to hartmark/OmniRoute
that referenced
this pull request
Sep 4, 2026
…er for unresolved tool nodes instead of "(empty)") into dev/omniroute-dev-combined
hartmark
added a commit
to hartmark/OmniRoute
that referenced
this pull request
Sep 4, 2026
…er for unresolved tool nodes instead of "(empty)") into dev/omniroute-dev-combined
hartmark
added a commit
to hartmark/OmniRoute
that referenced
this pull request
Sep 5, 2026
…er for unresolved tool nodes instead of "(empty)") into dev/omniroute-dev-combined
hartmark
added a commit
to hartmark/OmniRoute
that referenced
this pull request
Sep 6, 2026
…er for unresolved tool nodes instead of "(empty)") into dev/omniroute-dev-combined
hartmark
added a commit
to hartmark/OmniRoute
that referenced
this pull request
Sep 7, 2026
…er for unresolved tool nodes instead of "(empty)") into dev/omniroute-dev-combined
hartmark
added a commit
to hartmark/OmniRoute
that referenced
this pull request
Sep 7, 2026
…er for unresolved tool nodes instead of "(empty)") into dev/omniroute-dev-combined
hartmark
added a commit
to hartmark/OmniRoute
that referenced
this pull request
Sep 8, 2026
…er for unresolved tool nodes instead of "(empty)") into dev/omniroute-dev-combined
hartmark
added a commit
to hartmark/OmniRoute
that referenced
this pull request
Sep 8, 2026
…er for unresolved tool nodes instead of "(empty)") into dev/omniroute-dev-combined
hartmark
added a commit
to hartmark/OmniRoute
that referenced
this pull request
Sep 9, 2026
…er for unresolved tool nodes instead of "(empty)") into dev/omniroute-dev-combined
diegosouzapw
merged commit Sep 10, 2026
b516e95
into
diegosouzapw:release/v3.8.51
9 of 16 checks passed
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…instead of "(empty)" (diegosouzapw#12727) Validado numa worktree combinada com a onda de dashboard/monitoring desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-api-typecheck OK (289), check-file-size OK após rebaseline, 130/131 nos testes focados — a falha restante é asserção de tempo de parede sob carga, verde 6/6 isolada. "(empty)" para um nó de ferramenta ainda não resolvido é informação errada, não ausência de informação — o usuário lê como "não retornou nada". Spinner de pendente diz a verdade.
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
/dashboard/conversationsrendered every tool node with no content yetas a bare "(empty)" text bubble, whether it was a genuinely-purged node
or simply a node still waiting on its display content to resolve from
the owning call-log artifact (
resolveTurnDisplayContent). Liveconversations showed a confusing flash of "(empty)" bubbles before the
real tool call/result content populated a moment later.
toTurn()now mapsrole === "tool" && !textPreviewto a distinctpendingNormalizedBlockvariant, rendered as a spinner + "resolving…"instead. Every other mapping (real empty assistant text, resolved
tool_use/tool_result, resolved plain text) is unchanged.
toTurn()/ConversationTurnmoved out of the conversations page (a"use client"component that pulls inChatBubble/MarkdownMessage'sdependency tree) into a small colocated pure module
(
toTurn.ts), so this mapping stays unit-testable without abrowser/DOM harness. Net production LOC in
page.tsxgoes down.Related Issues
Validation
npm run lint(targeted: touched files clean)release/v3.8.51); focused checks rerun afterwardTests Added Or Updated
tests/unit/conversations-pending-tool-node.test.ts(new): proves thenew
pendingmapping for an unresolved tool node, proves a genuinelyempty assistant reply still renders as empty text (not pending), proves
a tool node renders its real content once
textPreviewlands, andproves
tool_use/tool_resultmapping is unaffected. Verified RED onpre-fix
toTurn()(falls back to{type:"text", text:"_(empty)_"}"),GREEN after.
Coverage Notes
src/app/(dashboard)/dashboard/conversations/{page.tsx,toTurn.ts},src/app/(dashboard)/dashboard/tools/traffic-inspector/components/chat/MessageContent.tsx,and
src/mitm/inspector/types.ts(newpendingNormalizedBlockvariant). The new test covers the entire
toTurn()mapping surfacedirectly; the spinner rendering itself is a small, visually-obvious
branch in
MessageContent.tsxmirroring the existingtool_use/tool_resultbranches.Reviewer Notes
state (
pending) plus a data-mapping refactor (moved, not rewritten).typecheck:dashboardwas run against this branch before and after thechange; the ~330 pre-existing errors on this release base are all
outside the touched files and unaffected by this change (verified via
git stash/git stash popdiff).