Greasy Fork is available in English.
Updates browser tab title to match the name of the chat.
// ==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);
})();