Skip to content

Commit c34ed90

Browse files
authored
fix(control-ui): disable refresh during active runs
Disable the Control UI refresh button while chat is disconnected, loading, sending, running, or streaming. This prevents manual chat-history refresh from racing active run/stream state and adds browser render coverage for the disabled-state matrix. Closes #65522. Validation: - Exact PR head `1511a086614a727fc4200730e7ad9622134bb7d3` reached `CLEAN` merge state. - GitHub CI for the exact head completed with no failed or pending checks.
1 parent e9d4cb2 commit c34ed90

2 files changed

Lines changed: 34 additions & 1 deletion

File tree

ui/src/ui/app-render.helpers.browser.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ function createState(overrides: Partial<AppViewState> = {}) {
1515
return {
1616
connected: true,
1717
chatLoading: false,
18+
chatRunId: null,
19+
chatSending: false,
20+
chatStream: null,
1821
onboarding: false,
1922
sessionKey: "main",
2023
sessionsHideCron: true,
@@ -49,6 +52,17 @@ function createState(overrides: Partial<AppViewState> = {}) {
4952
} as unknown as AppViewState;
5053
}
5154

55+
function renderRefreshButton(overrides: Partial<AppViewState> = {}) {
56+
const container = document.createElement("div");
57+
render(renderChatControls(createState(overrides)), container);
58+
59+
const button = container.querySelector<HTMLButtonElement>(
60+
`.chat-controls .btn--icon[data-tooltip="${t("chat.refreshTitle")}"]`,
61+
);
62+
expect(button).not.toBeNull();
63+
return button!;
64+
}
65+
5266
describe("chat header controls (browser)", () => {
5367
it("renders explicit hover tooltip metadata for the top-right action buttons", async () => {
5468
const container = document.createElement("div");
@@ -76,6 +90,19 @@ describe("chat header controls (browser)", () => {
7690
}
7791
});
7892

93+
it.each([
94+
["connected and idle", {}, false],
95+
["chat history loading", { chatLoading: true }, true],
96+
["chat send in flight", { chatSending: true }, true],
97+
["active run", { chatRunId: "run-123" }, true],
98+
["active stream", { chatStream: "streaming" }, true],
99+
["disconnected", { connected: false }, true],
100+
] as const)("sets refresh disabled state while %s", (_name, overrides, disabled) => {
101+
const button = renderRefreshButton(overrides);
102+
103+
expect(button.disabled).toBe(disabled);
104+
});
105+
79106
it("renders the cron session filter in the mobile dropdown controls", async () => {
80107
const state = createState({
81108
sessionsResult: {

ui/src/ui/app-render.helpers.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,12 @@ export function renderChatControls(state: AppViewState) {
222222
? t("chat.showCronSessionsHidden", { count: String(hiddenCronCount) })
223223
: t("chat.showCronSessions")
224224
: t("chat.hideCronSessions");
225+
const refreshDisabled =
226+
!state.connected ||
227+
state.chatLoading ||
228+
state.chatSending ||
229+
Boolean(state.chatRunId) ||
230+
state.chatStream !== null;
225231
const toolCallsIcon = html`
226232
<svg
227233
width="18"
@@ -275,7 +281,7 @@ export function renderChatControls(state: AppViewState) {
275281
<div class="chat-controls">
276282
<button
277283
class="btn btn--sm btn--icon"
278-
?disabled=${state.chatLoading || !state.connected}
284+
?disabled=${refreshDisabled}
279285
@click=${async () => {
280286
const app = state as unknown as ChatRefreshHost;
281287
app.chatManualRefreshInFlight = true;

0 commit comments

Comments
 (0)