Gemini Tab Namer

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

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

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

})();