Uh oh!
There was an error while loading. Please reload this page.
fix(plugin-chatbot): make AI-turn liveness real, not a fake clock - #1700
Merged
Conversation
The elapsed counter from #1699 ticked off a client-side setInterval, so it kept counting up even if the connection had dropped — falsely telling the user "still working" when nothing was happening. That's exactly the reassurance-theater we don't want. Replace it with liveness derived from REAL observed stream activity: - `useTurnLiveness(active, activityKey)` stamps the moment real data arrives (a streamed token / tool delta / `data-build-progress` update mutates the key) and measures seconds-since-last-byte. `live` is a genuine "the server sent us something recently" signal, not a free clock. - `LivenessIndicator` renders three honest states: • receiving (emerald pulse + m:ss) — bytes arrived recently • waiting (muted) — request in flight, nothing back yet (pre-first-token; never claims "receiving" when nothing received) • stalled (amber + "no response for Ns") — genuinely silent past 6s - Build panel feeds its real progress as the activity key, so a healthy build (which streams progress every few seconds) reads as receiving and a true stall flips to amber. A hard disconnect still surfaces via the error banner. Honest limitation (documented, follow-up): during a genuinely silent but alive server stretch the client can only say "no response for Ns" — proving "still connected" there needs a server-side SSE keep-alive heartbeat (framework). This change stops the lying; the heartbeat would let the quiet windows read green truthfully. Tests: 71/71. Four new ones render the real component under fake timers and assert the honest behavior — pre-first-token shows a neutral wait (not a fake m:ss), a quiet stream escalates to amber "…Ns", a streaming build reads as receiving, and a stalled build flips amber then recovers on the next byte. Typecheck + build clean; new code is lint-clean (no Date.now()/refs in render). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
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 freeto 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.
Why
The elapsed counter shipped in #1699 ticked off a client-side
setInterval— it kept counting up regardless of what the server was doing. If the connection dropped, it would happily keep showing "still working." That's reassurance-theater: it looks like liveness but proves nothing. (用户: "不能假装,要真的工作才对" — don't fake it, it has to actually work.)What
Drive the indicator off real observed stream activity instead of a clock.
useChat(AI SDK v6) surfaces server bytes by mutating its message parts — a streamed token, a tool delta, or adata-build-progressupdate. We turn that into an honest signal:useTurnLiveness(active, activityKey)— stamps the instant real data arrives (theactivityKeychanges) and measures seconds since the last byte.livemeans "the server genuinely sent us something in the last few seconds."LivenessIndicatorrenders three states, each a real fact:m:ss) — bytes arrived recentlyphase:done:items) as the activity key. A healthy build streams progress every few seconds → reads as receiving; a true stall flips to amber. A hard disconnect still surfaces via the existing error banner.Honest limitation (follow-up)
During a genuinely silent-but-alive server stretch (e.g. an LLM call inside a tool that emits nothing for >6s), the client can only truthfully say "no response for Ns" — it cannot prove the socket is alive from the client alone. Proving "still connected" there requires a server-side SSE keep-alive heartbeat (
encodeVercelDataStreamin frameworkservice-ai). This PR stops the lying; the heartbeat (separate, cross-repo) would let those quiet windows read green truthfully. Happy to do that next.Verification
m:ss), a quiet stream escalates to amber "…Ns", a streaming build reads as receiving, and a stalled build flips amber then recovers on the next byte.tsc --noEmitclean,vite buildclean. New code is lint-clean (noDate.now()/ref reads during render).Supersedes the clock-only behavior from #1699.
🤖 Generated with Claude Code