Enhancements from PR #45743: 1. Check streamSegments and live stream for session activity 2. Add min-height: 0 to .chat-main for flex layout fix 3. Expand test coverage to 6 cases (streaming scenarios) Retained improvements: - Three-layer defense (z-index, pointer-events) - Comprehensive PR description - Defense-in-depth against regressions Fixes #45707
This commit is contained in:
parent
a9b89e41ab
commit
9107f6e9bc
@ -9,6 +9,7 @@
|
||||
|
||||
.chat-main {
|
||||
min-width: 400px;
|
||||
min-height: 0; /* Prevent flex overflow that can hide input area */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
@ -1,43 +1,105 @@
|
||||
/**
|
||||
* Tests for #45707 - WebChat empty state overlay blocking input
|
||||
*
|
||||
* This test ensures that sessions with tool-call messages (heartbeat/cron),
|
||||
* streaming content, or live streams are not treated as empty, preventing
|
||||
* the welcome overlay from blocking the input box.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from "vitest";
|
||||
|
||||
describe("chat empty state logic (#45707)", () => {
|
||||
it("should not treat session with tool messages as empty", () => {
|
||||
const history: unknown[] = [];
|
||||
const tools: unknown[] = [{ role: "tool", content: "Heartbeat", timestamp: Date.now() }];
|
||||
const hasAnyMessages = history.length > 0 || tools.length > 0;
|
||||
const isEmpty = !hasAnyMessages;
|
||||
expect(hasAnyMessages).toBe(true);
|
||||
expect(isEmpty).toBe(false);
|
||||
const messages: unknown[] = [];
|
||||
const toolMessages: unknown[] = [{ role: "tool", content: "Heartbeat", timestamp: Date.now() }];
|
||||
const streamSegments: Array<{ text: string; ts: number }> = [];
|
||||
const stream: string | null = null;
|
||||
|
||||
const hasSessionActivity =
|
||||
messages.length > 0 ||
|
||||
toolMessages.length > 0 ||
|
||||
streamSegments.some((segment) => segment.text.trim()) ||
|
||||
stream !== null;
|
||||
|
||||
expect(hasSessionActivity).toBe(true);
|
||||
});
|
||||
|
||||
it("should treat truly empty session as empty", () => {
|
||||
const history: unknown[] = [];
|
||||
const tools: unknown[] = [];
|
||||
const hasAnyMessages = history.length > 0 || tools.length > 0;
|
||||
const isEmpty = !hasAnyMessages;
|
||||
expect(hasAnyMessages).toBe(false);
|
||||
expect(isEmpty).toBe(true);
|
||||
const messages: unknown[] = [];
|
||||
const toolMessages: unknown[] = [];
|
||||
const streamSegments: Array<{ text: string; ts: number }> = [];
|
||||
const stream: string | null = null;
|
||||
|
||||
const hasSessionActivity =
|
||||
messages.length > 0 ||
|
||||
toolMessages.length > 0 ||
|
||||
streamSegments.some((segment) => segment.text.trim()) ||
|
||||
stream !== null;
|
||||
|
||||
expect(hasSessionActivity).toBe(false);
|
||||
});
|
||||
|
||||
it("should not treat session with history messages as empty", () => {
|
||||
const history: unknown[] = [{ role: "user", content: "Hello", timestamp: Date.now() }];
|
||||
const tools: unknown[] = [];
|
||||
const hasAnyMessages = history.length > 0 || tools.length > 0;
|
||||
const isEmpty = !hasAnyMessages;
|
||||
expect(hasAnyMessages).toBe(true);
|
||||
expect(isEmpty).toBe(false);
|
||||
const messages: unknown[] = [{ role: "user", content: "Hello", timestamp: Date.now() }];
|
||||
const toolMessages: unknown[] = [];
|
||||
const streamSegments: Array<{ text: string; ts: number }> = [];
|
||||
const stream: string | null = null;
|
||||
|
||||
const hasSessionActivity =
|
||||
messages.length > 0 ||
|
||||
toolMessages.length > 0 ||
|
||||
streamSegments.some((segment) => segment.text.trim()) ||
|
||||
stream !== null;
|
||||
|
||||
expect(hasSessionActivity).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle mixed history and tool messages", () => {
|
||||
const history: unknown[] = [{ role: "user", content: "Check", timestamp: Date.now() }];
|
||||
const tools: unknown[] = [{ role: "tool", content: "Cron", timestamp: Date.now() }];
|
||||
const hasAnyMessages = history.length > 0 || tools.length > 0;
|
||||
const isEmpty = !hasAnyMessages;
|
||||
expect(hasAnyMessages).toBe(true);
|
||||
expect(isEmpty).toBe(false);
|
||||
it("should not treat session with streaming content as empty", () => {
|
||||
const messages: unknown[] = [];
|
||||
const toolMessages: unknown[] = [];
|
||||
const streamSegments: Array<{ text: string; ts: number }> = [
|
||||
{ text: "Streaming response...", ts: Date.now() },
|
||||
];
|
||||
const stream: string | null = null;
|
||||
|
||||
const hasSessionActivity =
|
||||
messages.length > 0 ||
|
||||
toolMessages.length > 0 ||
|
||||
streamSegments.some((segment) => segment.text.trim()) ||
|
||||
stream !== null;
|
||||
|
||||
expect(hasSessionActivity).toBe(true);
|
||||
});
|
||||
|
||||
it("should not treat session with live stream as empty", () => {
|
||||
const messages: unknown[] = [];
|
||||
const toolMessages: unknown[] = [];
|
||||
const streamSegments: Array<{ text: string; ts: number }> = [];
|
||||
const stream: string | null = "Live streaming...";
|
||||
|
||||
const hasSessionActivity =
|
||||
messages.length > 0 ||
|
||||
toolMessages.length > 0 ||
|
||||
streamSegments.some((segment) => segment.text.trim()) ||
|
||||
stream !== null;
|
||||
|
||||
expect(hasSessionActivity).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle mixed messages and streaming", () => {
|
||||
const messages: unknown[] = [{ role: "user", content: "Check", timestamp: Date.now() }];
|
||||
const toolMessages: unknown[] = [{ role: "tool", content: "Cron", timestamp: Date.now() }];
|
||||
const streamSegments: Array<{ text: string; ts: number }> = [
|
||||
{ text: "Thinking...", ts: Date.now() },
|
||||
];
|
||||
const stream: string | null = null;
|
||||
|
||||
const hasSessionActivity =
|
||||
messages.length > 0 ||
|
||||
toolMessages.length > 0 ||
|
||||
streamSegments.some((segment) => segment.text.trim()) ||
|
||||
stream !== null;
|
||||
|
||||
expect(hasSessionActivity).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@ -850,12 +850,16 @@ export function renderChat(props: ChatProps) {
|
||||
};
|
||||
|
||||
const chatItems = buildChatItems(props);
|
||||
// Fix #45707: Check total message count, not just renderable items
|
||||
// Sessions with tool-call messages (heartbeat/cron) should not be treated as empty
|
||||
const history = Array.isArray(props.messages) ? props.messages : [];
|
||||
const tools = Array.isArray(props.toolMessages) ? props.toolMessages : [];
|
||||
const hasAnyMessages = history.length > 0 || tools.length > 0;
|
||||
const isEmpty = !hasAnyMessages && !props.loading;
|
||||
// Fix #45707: Check for any session activity, not just renderable messages
|
||||
// Includes tool-call messages (heartbeat/cron), streaming content, and live streams
|
||||
const hasSessionActivity =
|
||||
(Array.isArray(props.messages) && props.messages.length > 0) ||
|
||||
(Array.isArray(props.toolMessages) && props.toolMessages.length > 0) ||
|
||||
(Array.isArray(props.streamSegments) &&
|
||||
props.streamSegments.some((segment) => segment.text.trim())) ||
|
||||
props.stream !== null;
|
||||
const showWelcomeState = !hasSessionActivity && !props.loading && !vs.searchOpen;
|
||||
const showEmptySearch = chatItems.length === 0 && !props.loading && vs.searchOpen;
|
||||
|
||||
const thread = html`
|
||||
<div
|
||||
@ -898,9 +902,9 @@ export function renderChat(props: ChatProps) {
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
${isEmpty && !vs.searchOpen ? renderWelcomeState(props) : nothing}
|
||||
${showWelcomeState ? renderWelcomeState(props) : nothing}
|
||||
${
|
||||
isEmpty && vs.searchOpen
|
||||
showEmptySearch
|
||||
? html`
|
||||
<div class="agent-chat__empty">No matching messages</div>
|
||||
`
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user