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