fix(ui): quiet the reconnect-wait queue row and hide its raw transport error (#111703)

This commit is contained in:
Peter Steinberger
2026-07-20 01:11:27 -07:00
committed by GitHub
parent 248726fafd
commit 83e78d8681
3 changed files with 62 additions and 6 deletions
+22
View File
@@ -567,6 +567,28 @@ describe("renderChatComposer controls", () => {
});
});
it("renders reconnect waits as quiet status without the raw transport error", () => {
const { container } = renderComposer({
queue: [
{
id: "reconnect-1",
text: "send me once the gateway is back",
createdAt: 1,
sendError: "chat.send unavailable during gateway restart",
sendState: "waiting-reconnect",
},
],
});
const item = container.querySelector(".chat-queue__item");
expect(item?.classList.contains("chat-queue__item--reconnect")).toBe(true);
expect(item?.querySelector(".chat-queue__dot")).not.toBeNull();
expect(item?.querySelector(".chat-queue__icon")).toBeNull();
expect(item?.querySelector(".chat-queue__error")).toBeNull();
const badge = item?.querySelector(".chat-queue__badge");
expect(badge?.textContent?.trim()).toBe("Waiting for reconnect");
expect(badge?.getAttribute("title")).toBe("chat.send unavailable during gateway restart");
});
it("renders failed sends as retryable and running commands as inert", () => {
const onQueueRetry = vi.fn();
let view = renderComposer({
+22 -6
View File
@@ -1087,6 +1087,7 @@ function renderChatQueueItem(item: ChatQueueItem, props: ChatQueueProps) {
const stateLabel = sendStateLabel(item);
const steered = isSteeredQueueItem(item);
const failed = item.sendState === "failed" || item.sendState === "unconfirmed";
const reconnecting = item.sendState === "waiting-reconnect";
const busy = item.sendState === "executing-command" || isInflightSteer(item);
const canSteer =
Boolean(props.canAbort && props.onQueueSteer) &&
@@ -1096,21 +1097,29 @@ function renderChatQueueItem(item: ChatQueueItem, props: ChatQueueProps) {
const text = item.text || (item.attachments?.length ? `Image (${item.attachments.length})` : "");
const itemClass = `chat-queue__item${steered ? " chat-queue__item--steered" : ""}${
failed ? " chat-queue__item--failed" : ""
}`;
}${reconnecting ? " chat-queue__item--reconnect" : ""}`;
// Row order keeps the actions on the first flex line; the error wraps below
// them via flex-basis so failed rows grow by one line instead of a card.
return html`
<div class=${itemClass}>
<span class="chat-queue__icon" aria-hidden="true">
${failed ? icons.alertTriangle : icons.clock}
</span>
${reconnecting
? html`<span class="chat-queue__dot" aria-hidden="true"></span>`
: html`<span class="chat-queue__icon" aria-hidden="true">
${failed ? icons.alertTriangle : icons.clock}
</span>`}
${renderChatAuthorAvatar(item.sender)}
${steered
? html`<span class="chat-queue__badge chat-queue__badge--steered"
>${t("chat.queue.steered")}</span
>`
: nothing}
${stateLabel ? html`<span class="chat-queue__badge">${stateLabel}</span>` : nothing}
${stateLabel
? html`<span
class="chat-queue__badge"
title=${ifDefined(reconnecting ? item.sendError : undefined)}
>${stateLabel}</span
>`
: nothing}
<span class="chat-queue__text" title=${text}>${text}</span>
<span class="chat-queue__actions">
${failed && props.onQueueRetry
@@ -1154,7 +1163,14 @@ function renderChatQueueItem(item: ChatQueueItem, props: ChatQueueProps) {
</openclaw-tooltip>
`}
</span>
${item.sendError ? html`<span class="chat-queue__error">${item.sendError}</span>` : nothing}
${
// Reconnect rows auto-retry, so the raw transport error is noise there;
// it stays inspectable via the badge tooltip. Failed/unconfirmed rows
// keep the visible error because the user must act on them.
item.sendError && !reconnecting
? html`<span class="chat-queue__error">${item.sendError}</span>`
: nothing
}
</div>
`;
}
+18
View File
@@ -2670,6 +2670,24 @@ td.data-table-key-col {
color: var(--danger);
}
/* Reconnect rows mirror the connection pill's amber status dot so the queue
reads as quiet auto-retry status, not a failure needing action. */
.chat-queue__dot {
flex-shrink: 0;
width: 7px;
height: 7px;
margin: 0 3.5px;
border-radius: var(--radius-full);
background: var(--warn);
box-shadow: 0 0 8px color-mix(in srgb, var(--warn) 55%, transparent);
animation: pulse-subtle 2s ease-in-out infinite;
}
.chat-queue__item--reconnect .chat-queue__badge {
background: color-mix(in srgb, var(--warn) 14%, transparent);
color: color-mix(in srgb, var(--warn) 78%, var(--text-strong));
}
.chat-queue__badge {
flex-shrink: 0;
padding: 1px 7px;