Greasy Fork is available in English.

Gemini Tab Namer

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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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

})();