Gemini Tab Namer

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

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==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);

})();