Gemini - Dynamic Tab Title

Automatically updates the browser tab title to the name of the current conversation.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name         Gemini - Dynamic Tab Title
// @namespace    http://tampermonkey.net/
// @version      1.1.0
// @description  Automatically updates the browser tab title to the name of the current conversation.
// @author       Te55eract, JonathanLU, and Gemini
// @match        *://gemini.google.com/*
// @icon         https://upload.wikimedia.org/wikipedia/commons/1/1d/Google_Gemini_icon_2025.svg
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- Configuration ---
    const CHAT_PAGE_REGEX = /^\/app\/[a-zA-Z0-9]+$/;
    const TITLE_POLL_INTERVAL_MS = 500; // Slower poll to save resources
    const DEFAULT_TITLE = 'Gemini';

    // --- State ---
    let activeTitlePollInterval = null;
    let lastKnownTitle = '';

    // --- Helper: Find the Title ---
    // This function attempts 3 strategies to find the title, making it resistant to updates.
    function getActiveChatTitle() {
        try {
            // Strategy 1: Accessibility Attribute (Most Robust)
            // Google uses aria-selected="true" for the active sidebar item for screen readers.
            const ariaSelected = document.querySelector('[role="navigation"] [aria-selected="true"]');
            if (ariaSelected) {
                // We want the text inside, but we need to be careful not to grab "options" button text.
                // Usually the title is the distinct text node or inside a specific div.
                // Let's try to find a title-like element inside the active container.
                const titleSpan = ariaSelected.querySelector('.conversation-title, [data-test-id="conversation-title"]');
                if (titleSpan) return titleSpan.textContent.trim();

                // Fallback: Just grab the text of the container if specific class is missing
                // Clean up newlines to avoid grabbing date headers if the selection is weird
                return ariaSelected.textContent.replace(/[\n\r]+.*/g, '').trim();
            }

            // Strategy 2: URL Matching
            // Find the link in the sidebar that matches the current browser URL
            const currentPath = window.location.pathname;
            if (currentPath.includes('/app/')) {
                const activeLink = document.querySelector(`a[href$="${currentPath}"]`);
                if (activeLink) {
                    return activeLink.textContent.trim();
                }
            }
        } catch (e) {
            // Fail silently
        }
        return null;
    }

    function isChatPage() {
        return CHAT_PAGE_REGEX.test(location.pathname);
    }

    function stopActiveTitlePoll() {
        if (activeTitlePollInterval) {
            clearInterval(activeTitlePollInterval);
            activeTitlePollInterval = null;
        }
    }

    function startNavigationPoll() {
        stopActiveTitlePoll();

        // Immediate check
        updateTitle();

        // Persistent check
        activeTitlePollInterval = setInterval(() => {
            if (!isChatPage()) {
                if (document.title !== DEFAULT_TITLE) document.title = DEFAULT_TITLE;
                return;
            }
            updateTitle();
        }, TITLE_POLL_INTERVAL_MS);
    }

    function updateTitle() {
        const foundTitle = getActiveChatTitle();

        // If we found a title and it's different from the last one we set
        if (foundTitle && foundTitle !== lastKnownTitle) {
            lastKnownTitle = foundTitle;
            const newDocTitle = `${lastKnownTitle} - ${DEFAULT_TITLE}`;
            if (document.title !== newDocTitle) {
                document.title = newDocTitle;
            }
        }
    }

    function setupHistoryHook() {
        const originalPushState = history.pushState;
        const originalReplaceState = history.replaceState;

        const handleNav = () => {
            // Reset known title on nav so we force a re-check
            lastKnownTitle = '';
            startNavigationPoll();
        };

        history.pushState = function(...args) {
            const res = originalPushState.apply(this, args);
            handleNav();
            return res;
        };

        history.replaceState = function(...args) {
            const res = originalReplaceState.apply(this, args);
            handleNav();
            return res;
        };

        window.addEventListener('popstate', handleNav);
    }

    // --- Init ---
    function initialize() {
        setupHistoryHook();
        startNavigationPoll();
    }

    initialize();
})();