Gemini Tab Namer

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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);

})();