From 78825403ad956501c9b673d42ee4ed842c4c4289 Mon Sep 17 00:00:00 2001 From: can1357 Date: Fri, 31 Jul 2026 19:01:33 +0200 Subject: [PATCH] fix(coding-agent): replay queued appearance changes --- .../src/modes/interactive-mode.ts | 11 +++++--- .../test/interactive-theme-scrollback.test.ts | 25 +++++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/packages/coding-agent/src/modes/interactive-mode.ts b/packages/coding-agent/src/modes/interactive-mode.ts index 91094d013..13a9bdb4d 100644 --- a/packages/coding-agent/src/modes/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive-mode.ts @@ -1167,13 +1167,16 @@ export class InteractiveMode implements InteractiveModeContext { this.ui.terminal.onAppearanceChange((mode, requestToken) => { const request = this.#appearanceRefreshRequest; const appearanceRefreshWasRequested = - request !== undefined && requestToken === request.token && Date.now() <= request.deadline; + request !== undefined && + Date.now() <= request.deadline && + (requestToken === request.token || requestToken === undefined); if (request !== undefined && requestToken === request.token) { this.#appearanceRefreshRequest = undefined; } - // Ctrl+L already replays immediately below. If its asynchronous OSC 11 - // response reveals a theme change, commit that change so theme loading - // performs a second full replay with the newly detected palette. + // Ctrl+L already replays immediately below. If either its asynchronous + // OSC 11 response or an automatic query ahead of it reveals a theme + // change, commit that change so theme loading performs a second full + // replay with the newly detected palette. onTerminalAppearanceChange(mode, appearanceRefreshWasRequested ? {} : undefined); }); diff --git a/packages/coding-agent/test/interactive-theme-scrollback.test.ts b/packages/coding-agent/test/interactive-theme-scrollback.test.ts index 8293dece6..ca1525bf0 100644 --- a/packages/coding-agent/test/interactive-theme-scrollback.test.ts +++ b/packages/coding-agent/test/interactive-theme-scrollback.test.ts @@ -224,6 +224,31 @@ describe("InteractiveMode theme scrollback refresh", () => { expect(writes.join("").split("\x1b[3J")).toHaveLength(3); }); + it("replays with the new palette when an automatic response wins the queued Ctrl+L race", async () => { + terminal.emitAppearanceReport("dark"); + enableAutoTheme(); + await terminal.waitForRender(); + const fullRedraws = mode.ui.fullRedraws; + const writes: string[] = []; + const realWrite = terminal.write.bind(terminal); + vi.spyOn(terminal, "write").mockImplementation(data => { + writes.push(data); + realWrite(data); + }); + terminal.appearanceOnRefresh = "light"; + + const epoch = getThemeEpoch(); + terminal.sendInput("\x0c"); + terminal.emitAppearanceReport("light"); + await waitForThemeEpochToAdvance(epoch); + await terminal.waitForRender(); + await Promise.resolve(); + + expect(getCurrentThemeName()).toBe("light"); + expect(mode.ui.fullRedraws).toBe(fullRedraws + 2); + expect(writes.join("").split("\x1b[3J")).toHaveLength(3); + }); + it("owns a synchronous refresh response before invoking the terminal", async () => { terminal.emitAppearanceReport("dark"); enableAutoTheme();