From 0ec6ed20cbcc081ca32b610207eff65640c52b66 Mon Sep 17 00:00:00 2001 From: Engel Nyst Date: Sat, 16 Aug 2025 09:00:45 +0200 Subject: [PATCH] fix(frontend): browser tab notification respects user-renamed titles; add unit test (#10406) --- frontend/__tests__/utils/browser-tab.test.ts | 51 ++++++++++++++++++++ frontend/src/utils/browser-tab.ts | 17 ++++--- 2 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 frontend/__tests__/utils/browser-tab.test.ts diff --git a/frontend/__tests__/utils/browser-tab.test.ts b/frontend/__tests__/utils/browser-tab.test.ts new file mode 100644 index 0000000000..72ccfc394e --- /dev/null +++ b/frontend/__tests__/utils/browser-tab.test.ts @@ -0,0 +1,51 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; + +import { browserTab } from "#/utils/browser-tab"; + +// These tests exercise the browser-tab notification flasher behavior. +// Specifically we verify that when the document title changes externally +// while a notification is active, the flasher updates its internal +// baseline so it restores/toggles to the new title instead of an old one. + +describe("browserTab notifications", () => { + const MESSAGE = "Agent ready"; + const INITIAL = "Conversation 123 | OpenHands"; + const RENAMED = "My renamed title | OpenHands"; + + beforeEach(() => { + vi.useFakeTimers(); + // reset title for each test + document.title = INITIAL; + }); + + afterEach(() => { + browserTab.stopNotification(); + vi.runOnlyPendingTimers(); + vi.useRealTimers(); + }); + + it("updates baseline when title changes during an active notification and restores to the new title", () => { + // Start flashing + browserTab.startNotification(MESSAGE); + + // Tick once: should switch to the message + vi.advanceTimersByTime(1000); + expect(document.title).toBe(MESSAGE); + + // Simulate an external rename while flashing (e.g., user edits title) + document.title = RENAMED; + + // Next tick: flasher observes the external change and updates baseline + vi.advanceTimersByTime(1000); + // On this tick, we toggle back to the message + expect(document.title).toBe(MESSAGE); + + // Next tick should toggle to the updated baseline (renamed title) + vi.advanceTimersByTime(1000); + expect(document.title).toBe(RENAMED); + + // Stop flashing: title should remain the updated baseline + browserTab.stopNotification(); + expect(document.title).toBe(RENAMED); + }); +}); diff --git a/frontend/src/utils/browser-tab.ts b/frontend/src/utils/browser-tab.ts index 8fd1848a59..bed71abc82 100644 --- a/frontend/src/utils/browser-tab.ts +++ b/frontend/src/utils/browser-tab.ts @@ -11,20 +11,23 @@ export const browserTab = { startNotification(message: string) { if (!isBrowser) return; - // Store original title if not already stored - if (!originalTitle) { - originalTitle = document.title; - } + // Always capture the current title as the baseline to restore to + originalTitle = document.title; // Clear any existing interval if (titleInterval) { this.stopNotification(); } - // Alternate between original title and notification message + // Alternate between the latest baseline title and the notification message. + // If the title changes externally (e.g., user renames conversation), + // update the baseline so we restore to the new value when stopping. titleInterval = window.setInterval(() => { - document.title = - document.title === originalTitle ? message : originalTitle; + const current = document.title; + if (current !== originalTitle && current !== message) { + originalTitle = current; + } + document.title = current === message ? originalTitle : message; }, 1000); // Set favicon to indicate notification