style: reformat ChatStream/ChatView (line-width normalization) from review pass

This commit is contained in:
2026-09-01 15:21:42 +00:00
parent 7ff9f77f40
commit c8cde3ff89
3 changed files with 60 additions and 74 deletions
+9 -2
View File
@@ -158,7 +158,9 @@ describe("Bubble", () => {
expect(screen.getByText("$ ls -la")).toBeInTheDocument(); expect(screen.getByText("$ ls -la")).toBeInTheDocument();
expect(screen.getByText("finished")).toBeInTheDocument(); expect(screen.getByText("finished")).toBeInTheDocument();
const summary = screen.getByText("$ ls -la").closest("summary") as HTMLElement; const summary = screen
.getByText("$ ls -la")
.closest("summary") as HTMLElement;
const card = summary.closest("details") as HTMLDetailsElement; const card = summary.closest("details") as HTMLDetailsElement;
expect(card.open).toBe(false); expect(card.open).toBe(false);
await userEvent.click(summary); await userEvent.click(summary);
@@ -1029,7 +1031,12 @@ describe("unified tool card", () => {
expect(unqueue).toHaveBeenCalledWith(0); expect(unqueue).toHaveBeenCalledWith(0);
rerender( rerender(
stream({ messages: [msg({ role: "user", text: "hi" })], busy: true, queued: [], unqueue }), stream({
messages: [msg({ role: "user", text: "hi" })],
busy: true,
queued: [],
unqueue,
}),
); );
expect(screen.queryByText("next msg")).toBeNull(); expect(screen.queryByText("next msg")).toBeNull();
}); });
+29 -33
View File
@@ -147,9 +147,7 @@ function oneLine(text: string): string {
* (which carries the output preview) is already rendered */ * (which carries the output preview) is already rendered */
function resultSkipped(m: ChatMessage, tools: Map<string, ToolState>): boolean { function resultSkipped(m: ChatMessage, tools: Map<string, ToolState>): boolean {
return ( return (
m.role === "toolResult" && m.role === "toolResult" && m.toolCallId !== null && tools.has(m.toolCallId)
m.toolCallId !== null &&
tools.has(m.toolCallId)
); );
} }
@@ -602,9 +600,7 @@ export default function ChatStream({
const q: string = query.trim().toLowerCase(); const q: string = query.trim().toLowerCase();
if (q.length === 0) return []; if (q.length === 0) return [];
return messages return messages
.filter( .filter((m) => !resultSkipped(m, tools) && searchHaystack(m).includes(q))
(m) => !resultSkipped(m, tools) && searchHaystack(m).includes(q),
)
.map((m) => m.key); .map((m) => m.key);
}, [messages, tools, query]); }, [messages, tools, query]);
@@ -657,34 +653,34 @@ export default function ChatStream({
</button> </button>
</div> </div>
)} )}
{messages.map((m) => ( {messages.map((m) => (
<div <div
key={m.key} key={m.key}
className={m.key === currentKey ? "msg-current" : undefined} className={m.key === currentKey ? "msg-current" : undefined}
ref={(el: HTMLDivElement | null): void => { ref={(el: HTMLDivElement | null): void => {
if (el === null) rowRefs.current.delete(m.key); if (el === null) rowRefs.current.delete(m.key);
else rowRefs.current.set(m.key, el); else rowRefs.current.set(m.key, el);
}} }}
> >
<Bubble msg={m} tools={tools} query={query} showTs={showTs} /> <Bubble msg={m} tools={tools} query={query} showTs={showTs} />
</div>
))}
{queued.map((text, i) => (
<div className="bubble-row user" key={`q-${i}-${text}`}>
<div className="bubble queued-bubble">
<div className="queued-line">{text}</div>
<button
type="button"
className="icon-btn queued-remove"
aria-label="Remove queued message"
onClick={() => unqueue(i)}
>
</button>
</div> </div>
</div> ))}
))} {queued.map((text, i) => (
{showTyping && <TypingIndicator />} <div className="bubble-row user" key={`q-${i}-${text}`}>
<div className="bubble queued-bubble">
<div className="queued-line">{text}</div>
<button
type="button"
className="icon-btn queued-remove"
aria-label="Remove queued message"
onClick={() => unqueue(i)}
>
</button>
</div>
</div>
))}
{showTyping && <TypingIndicator />}
</div> </div>
</div> </div>
{searchOpen && ( {searchOpen && (
+22 -39
View File
@@ -142,15 +142,11 @@ export default function ChatView({ store, pushToast }: Props) {
(incoming: EventFrame[]): void => { (incoming: EventFrame[]): void => {
setEvents((prev) => { setEvents((prev) => {
const merged = mergeEvents(prev, incoming); const merged = mergeEvents(prev, incoming);
lastSeqRef.current = Math.max( lastSeqRef.current = Math.max(lastSeqRef.current, lastPersistedSeq(merged));
lastSeqRef.current,
lastPersistedSeq(merged),
);
return merged; return merged;
}); });
// a finished run means usage changed server-side // a finished run means usage changed server-side
if (incoming.some((e) => e.type === EventType.AgentSettled)) if (incoming.some((e) => e.type === EventType.AgentSettled)) refreshStats();
refreshStats();
}, },
[refreshStats], [refreshStats],
); );
@@ -229,14 +225,13 @@ export default function ChatView({ store, pushToast }: Props) {
const toolNow: string | undefined = [...chat.tools.values()] const toolNow: string | undefined = [...chat.tools.values()]
.filter((t) => t.running) .filter((t) => t.running)
.map((t) => t.name)[0]; .map((t) => t.name)[0];
const titleClaim: string | null = const titleClaim: string | null = busy
busy ? streaming
? streaming ? `${label} · writing…`
? `${label} · writing…` : toolNow === undefined
: toolNow === undefined ? `${label} · working…`
? `${label} · working…` : `${label} · ${toolNow}`
: `${label} · ${toolNow}` : label;
: label;
useTitle(titleClaim); useTitle(titleClaim);
const minSeq: number = useMemo( const minSeq: number = useMemo(
@@ -279,9 +274,7 @@ export default function ChatView({ store, pushToast }: Props) {
const t = e.target; const t = e.target;
if ( if (
t instanceof HTMLElement && t instanceof HTMLElement &&
(t.tagName === "INPUT" || (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.isContentEditable)
t.tagName === "TEXTAREA" ||
t.isContentEditable)
) )
return; return;
e.preventDefault(); e.preventDefault();
@@ -404,10 +397,7 @@ export default function ChatView({ store, pushToast }: Props) {
} }
}; };
const pickModel = async ( const pickModel = async (provider: string, modelId: string): Promise<void> => {
provider: string,
modelId: string,
): Promise<void> => {
if (switching) return; if (switching) return;
setSwitching(true); setSwitching(true);
try { try {
@@ -494,8 +484,7 @@ export default function ChatView({ store, pushToast }: Props) {
} }
// empty composer + history: ArrowUp recalls the last sent message // empty composer + history: ArrowUp recalls the last sent message
if (e.key === ARROW_UP_KEY && draft.length === 0) { if (e.key === ARROW_UP_KEY && draft.length === 0) {
const last: string | undefined = const last: string | undefined = sentRef.current[sentRef.current.length - 1];
sentRef.current[sentRef.current.length - 1];
if (last !== undefined) { if (last !== undefined) {
e.preventDefault(); e.preventDefault();
setDraft(last); setDraft(last);
@@ -601,17 +590,15 @@ export default function ChatView({ store, pushToast }: Props) {
</div> </div>
)} )}
</div> </div>
{stats !== null && {stats !== null && (stats.inputTokens > 0 || stats.outputTokens > 0) && (
(stats.inputTokens > 0 || stats.outputTokens > 0) && ( <span
<span className="usage-chip"
className="usage-chip" title={`${stats.inputTokens.toLocaleString()}${stats.outputTokens.toLocaleString()} · ${stats.turns} turns · $${stats.totalCost.toFixed(2)}`}
title={`${stats.inputTokens.toLocaleString()}${stats.outputTokens.toLocaleString()} · ${stats.turns} turns · $${stats.totalCost.toFixed(2)}`} >
> {formatTokens(stats.inputTokens)} {formatTokens(stats.outputTokens)} ·{" "}
{formatTokens(stats.inputTokens)} {stats.turns} turns · ${stats.totalCost.toFixed(2)}
{formatTokens(stats.outputTokens)} · {stats.turns} turns · $ </span>
{stats.totalCost.toFixed(2)} )}
</span>
)}
<span <span
className={classNames( className={classNames(
"conn-dot", "conn-dot",
@@ -641,11 +628,7 @@ export default function ChatView({ store, pushToast }: Props) {
</button> </button>
{menuOpen && ( {menuOpen && (
<div <div className="chat-menu" role="menu" aria-label="Session actions">
className="chat-menu"
role="menu"
aria-label="Session actions"
>
<button <button
type="button" type="button"
className="chat-menu-item" className="chat-menu-item"