Gemini Tab Namer

Updates browser tab title to match the name of the chat.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name         Gemini Tab Namer
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Updates browser tab title to match the name of the chat.
// @author       lowpr0file
// @match        https://gemini.google.com/*
// @grant        none
// @run-at       document-idle
// @noframes
// ==/UserScript==

(function() {
    'use strict';

    const LOG_PREFIX = "[GeminiTabNamer]";

    // --- SELECTORS ---
    // We trust the Sidebar because diagnostics proved it contains the real user-set titles.
    function getActiveChatName() {
        // 1. Find Nav container
        const nav = document.querySelector('nav') || document.querySelector('[role="navigation"]');
        if (!nav) return null;

        // 2. Find Active Link
        // Looks for class .selected or accessibility attribute aria-selected="true"
        const activeItem = nav.querySelector('a.selected, a[aria-selected="true"]');

        if (activeItem) {
            const rawText = activeItem.innerText || "";
            // Clean up the text (remove newlines/icons)
            return rawText.split(/[\n\r]/)[0].trim();
        }
        return null;
    }

    // --- STATE ---
    let currentChatName = "";
    let lastUrl = location.href;
    let titleObserver = null;

    // --- LOGIC ---
    function enforceTitle() {
        const chatName = getActiveChatName();

        if (chatName && chatName.length > 0) {
            // Filter out defaults to avoid overwriting the title while loading
            if (chatName === "Gemini" || chatName === "New chat" || chatName === "Conversation with Gemini") return;

            // Update Target State
            if (currentChatName !== chatName) {
                currentChatName = chatName;
            }

            // Execute Rename if the browser tab has drifted
            if (document.title !== currentChatName) {
                document.title = currentChatName;
            }
        }
    }

    // --- THE BOUNCER (MutationObserver on Title) ---
    // This watches the <title> tag specifically. If Gemini changes the title 
    // (e.g. during a split-screen resize), this changes it back instantly.
    function startTitleGuard() {
        const titleEl = document.querySelector('title');
        if (!titleEl) return;

        // If we already have a guard, don't add another one
        if (titleObserver) return;

        titleObserver = new MutationObserver((mutations) => {
            // If we have a valid target name, and the title just changed to something else...
            if (currentChatName && document.title !== currentChatName) {
                // ...Change it back IMMEDIATELY.
                document.title = currentChatName;
            }
        });

        // Watch specifically for changes to the text inside <title>
        titleObserver.observe(titleEl, { childList: true, characterData: true, subtree: true });
    }

    // --- HEARTBEAT ---
    // Ensures the script stays alive and handles URL navigation
    function heartBeat() {
        // 1. Navigation Check
        if (location.href !== lastUrl) {
            lastUrl = location.href;
            enforceTitle();
        }

        // 2. Ensure Guard is alive (SPA frameworks sometimes delete the <title> tag and make a new one)
        if (!titleObserver || !document.contains(document.querySelector('title'))) {
             if (titleObserver) titleObserver.disconnect();
             titleObserver = null;
             startTitleGuard();
        }

        // 3. Regular Enforcement (Backup)
        enforceTitle();
    }

    // --- LAUNCH ---
    console.log(LOG_PREFIX, "v1.0 Active (The Bouncer).");
    
    // Initial delay to allow page hydration
    setTimeout(() => {
        enforceTitle();
        startTitleGuard();
    }, 1000);

    // Run the heartbeat every second
    setInterval(heartBeat, 1000);

})();