Gemini Conversation Folders

Adds folders with a settings menu to the Google Gemini sidebar to organize conversations.

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

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

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

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

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

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

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Gemini Conversation Folders
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Adds folders with a settings menu to the Google Gemini sidebar to organize conversations.
// @author       T. Berkeley Goodloe
// @match        https://gemini.google.com/app*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @require      https://cdn.jsdelivr.net/npm/[email protected]/Sortable.min.js
// @license      none
// ==/UserScript==

// -----------------------------------------------------------------------------
// Script: Gemini Conversation Folders
// Author: T. Berkeley Goodloe
// Created: 2025-08-07
// Version: 1.0
// Contact: [email protected]
// Description: Sidebar folders for Google Gemini conversations.
//              Adds an inline “Folders + ⚙︎” header.
//
//              ⚙︎ gear menu actions:
//              • Copy Debug Code  → copies a JSON snapshot to the clipboard containing:
//                   – storedFolders (array of folder objects)
//                   – storedConversationFolders (map of conversation‑id → folder‑id)
//                   – domFolders (ids currently in each folder element)
//                   – domOrphans (ids still loose in the main list)
//              • Reset Folder Data  → deletes Tampermonkey keys “gemini_folders” &
//                   “gemini_convo_folders” and reloads the page, returning you to a
//                   fresh empty state.
// -----------------------------------------------------------------------------

(function() {
    'use strict';

    // --- SELECTORS ---
    const CHAT_ITEM_SELECTOR = 'div[data-test-id="conversation"]';
    const CHAT_CONTAINER_SELECTOR = '.conversation-items-container';
    const CHAT_LIST_CONTAINER_SELECTOR = 'conversations-list .conversations-container';
    const INJECTION_POINT_SELECTOR = 'div.chat-history-list';

    // --- STYLES ---
    GM_addStyle(`
    #folder-ui-container { padding: 0 8px; }
    #folder-container { padding-bottom: 8px; border-bottom: 1px solid var(--surface-3); }
    .folder { margin-bottom: 5px; border-radius: 8px; overflow: hidden; }
    .folder-header { display: flex; align-items: center; padding: 10px; cursor: pointer; background-color: var(--surface-2); position: relative; }
    .folder-header:hover { background-color: var(--surface-3); }
    .folder-color-indicator { width: 8px; height: 20px; border-radius: 4px; margin-right: 10px; flex-shrink: 0; }
    .folder-name { flex-grow: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-family: 'Roboto', Arial, sans-serif !important; }
    .folder-controls { display: flex; align-items: center; margin-left: 5px; }
    .folder-toggle-icon { transition: transform 0.2s; }
    .folder.closed .folder-toggle-icon { transform: rotate(-90deg); }
    .folder-options-btn { background: none; border: none; color: inherit; cursor: pointer; padding: 2px 4px; border-radius: 4px; margin-left: 4px; font-size: 1.2em; line-height: 1; }
    .folder-options-btn:hover { background-color: rgba(255,255,255,0.1); }
    .folder-content { max-height: 500px; overflow-y: auto; transition: max-height 0.3s ease-in-out, padding 0.3s ease-in-out; background-color: var(--surface-1); }
    .folder.closed .folder-content { max-height: 0; padding-top: 0; padding-bottom: 0; }
    #add-folder-btn { width: 100%; margin: 8px 0; padding: 10px; border: none; background-color: var(--primary-surface); color: var(--on-primary-surface); border-radius: 8px; cursor: pointer; font-weight: 500; }
    #add-folder-btn:hover { opacity: 0.9; }
    .sortable-ghost { opacity: 0.4; background: var(--primary-surface-hover); }
    .conversation-items-container { cursor: grab; }
    .folder-context-menu { position: absolute; z-index: 10000; background-color: #333333; border: 1px solid var(--surface-4); border-radius: 8px; padding: 5px; box-shadow: 0 4px 8px rgba(0,0,0,0.2); display: none; }
    .folder-context-menu-item { padding: 8px 12px; cursor: pointer; border-radius: 4px; white-space: nowrap; font-family: 'Roboto', Arial, sans-serif !important; color: #FFFFFF; }
    .folder-context-menu-item:hover { background-color: var(--surface-4); }
    .folder-context-menu-item.delete { color: #DB4437; }

    /* ---- Color-picker ---- */
    .color-picker-dialog .color-swatch {
        width: 32px;
        height: 32px;
        border-radius: 50%;
        cursor: pointer;
        border: 2px solid transparent;
        position: relative;          /* for the ring ::after */
    }
    .color-picker-dialog .color-swatch:hover {
        border: 2px solid var(--on-primary-surface);
    }
    .color-picker-dialog .color-swatch.selected {
        border: 2px solid transparent;               /* remove hover border */
    }
    .color-picker-dialog .color-swatch.selected::after {
        content: "";
        position: absolute;
        inset: 0;
        border: 3px solid #fff;                      /* ring color / thickness */
        border-radius: 50%;
        box-sizing: border-box;
        pointer-events: none;
    }
        /* ─── Header row ─────────────────── */
    #folders-header{
        display:flex;align-items:center;margin:8px 0 12px;
        font-family:'Roboto', Arial, sans-serif;
    }
    .fh-title   { font-weight:600; }
    .fh-spacer  { flex:1; }
    #fh-add,#fh-gear{
        background:none;border:none;color:inherit;
        font-size:18px;cursor:pointer;margin-left:6px;
        width:28px;height:28px;display:flex;align-items:center;justify-content:center;
        border-radius:4px;
    }
    #fh-add:hover,#fh-gear:hover{ background:rgba(255,255,255,.1); }

    /* ─── Gear pop-over ───────────────── */
    #folder-tools-pop{
        position:absolute;z-index:9999;
        background:#333;border:1px solid var(--surface-4);
        border-radius:8px;padding:6px;box-shadow:0 4px 10px rgba(0,0,0,.3);
    }
    #folder-tools-pop .tools-item{
        display:block;width:100%;text-align:left;
        background:none;border:none;color:#fff;
        padding:8px 14px;cursor:pointer;border-radius:6px;
        font-family:'Roboto', Arial, sans-serif;font-size:14px;
    }
    #folder-tools-pop .tools-item:hover{
        background:var(--surface-4);
    }

    #reset-data-btn { position: fixed; bottom: 15px; right: 15px; z-index: 9999; background-color: #DB4437; color: white; border: none; border-radius: 8px; padding: 10px 15px; font-family: 'Roboto', Arial, sans-serif; font-size: 14px; cursor: pointer; box-shadow: 0 2px 5px rgba(0,0,0,0.3); }
    .custom-dialog-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(34, 34, 34, 0.75); z-index: 20000; display: flex; align-items: center; justify-content: center; }
    .custom-dialog-box { background-color: #333333; padding: 25px; border-radius: 12px; box-shadow: 0 5px 15px rgba(0,0,0,0.3); text-align: center; max-width: 400px; border: 1px solid var(--surface-4); }
    .custom-dialog-box p, .custom-dialog-box h2 { margin: 0 0 20px; font-family: 'Roboto', Arial, sans-serif; color: #FFFFFF; }
    .custom-dialog-btn { border: none; border-radius: 8px; padding: 10px 20px; cursor: pointer; font-weight: 500; margin: 0 10px; }
    .dialog-btn-confirm { background-color: #8ab4f8; color: #202124; }
    .dialog-btn-delete { background-color: #DB4437; color: white; }
    .dialog-btn-cancel { background-color: var(--surface-4); color: var(--on-surface); }
    .custom-dialog-input { width: 100%; box-sizing: border-box; padding: 10px; border-radius: 8px; border: 1px solid var(--surface-4); background-color: var(--surface-1); color: var(--on-surface); font-size: 16px; margin-bottom: 20px; }
    .color-picker-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; margin-bottom: 20px; }
`);

    // --- STATE MANAGEMENT ---
    let folders = [];
    let conversationFolders = {};
    let chatItemCache = new Map();
    const FOLDER_COLORS = ['#370000', '#0D3800', '#001B38', '#383200', '#380031', '#7DAC89', '#7A82AF', '#AC7D98', '#7AA7AF', '#9CA881'];

    async function loadData() {
        folders = await GM_getValue('gemini_folders', []);
        conversationFolders = await GM_getValue('gemini_convo_folders', {});
    }

    async function saveData() {
        await GM_setValue('gemini_folders', folders);
        await GM_setValue('gemini_convo_folders', conversationFolders);
    }

    // ─── UNIQUE-ID DETECTION ─────────────────────────────────────────
    function getIdentifierFromElement(el) {
        if (!el) return null;

        // If we were handed the container, drill down once to the conversation div
        if (el.matches('.conversation-items-container')) {
            el = el.querySelector('[data-test-id="conversation"]') || el;
        }

        /* 1. New URL-style id ( /conversation/<id> )  */
        const anchor = el.closest('a');
        if (anchor) {
            const href = anchor.getAttribute('href') || '';
            const m = href.match(/\/conversation\/([A-Za-z0-9_-]+)/);
            if (m) return m[1];
        }

        /* 2. jslog metadata ( c_<id> )  */
        const jslog = el.getAttribute('jslog') || '';
        let m = jslog.match(/"c_([A-Za-z0-9_-]+)"/); // within quotes
        if (!m) m = jslog.match(/c_([A-Za-z0-9_-]+)/); // bare
        if (m) return m[1];

        /* 3. Fallback to visible title text (last resort) */
        const t = el.querySelector('.conversation-title');
        if (t) return `title:${t.textContent.trim()}`;

        /* 4. Couldn’t identify – log once per element */
        console.warn('[Gemini Folders] Could not find ID for element:', el);
        return null;
    }

    // --- UI RENDERING ---
    function renderFolders() {
        const container = document.getElementById('folder-container');
        if (!container) return;
        while (container.firstChild) container.removeChild(container.firstChild);

        folders.forEach(folder => {
            const folderEl = document.createElement('div');
            folderEl.className = 'folder';
            folderEl.dataset.folderId = folder.id;
            if (folder.isClosed) folderEl.classList.add('closed');

            const headerEl = document.createElement('div');
            headerEl.className = 'folder-header';
            headerEl.addEventListener('click', (e) => {
                if (!e.target.closest('.folder-options-btn')) toggleFolder(folder.id);
            });

            const colorIndicator = document.createElement('div');
            colorIndicator.className = 'folder-color-indicator';
            colorIndicator.style.backgroundColor = folder.color;

            const nameEl = document.createElement('span');
            nameEl.className = 'folder-name';
            nameEl.textContent = folder.name;

            const controlsEl = document.createElement('div');
            controlsEl.className = 'folder-controls';

            const toggleIcon = document.createElement('span');
            toggleIcon.className = 'folder-toggle-icon';
            toggleIcon.textContent = '▼';

            const optionsBtn = document.createElement('button');
            optionsBtn.className = 'folder-options-btn';
            optionsBtn.textContent = '⋮';
            optionsBtn.addEventListener('click', (e) => showContextMenu(e, folder.id));

            controlsEl.appendChild(toggleIcon);
            controlsEl.appendChild(optionsBtn);
            headerEl.appendChild(colorIndicator);
            headerEl.appendChild(nameEl);
            headerEl.appendChild(controlsEl);

            const contentEl = document.createElement('div');
            contentEl.className = 'folder-content';

            folderEl.appendChild(headerEl);
            folderEl.appendChild(contentEl);
            container.appendChild(folderEl);
        });

        organizeConversations();
        setupDragAndDrop();
    }

    // --- FIXED FUNCTION: This is where the magic happens ---
    function organizeConversations() {
        const chatListContainer = document.querySelector(CHAT_LIST_CONTAINER_SELECTOR);
        if (!chatListContainer) return;

        const folderIds = new Set(folders.map(f => f.id));
        let dataWasCorrected = false;

        // Only move orphaned chats back to main (not ALL chats!)
        document.querySelectorAll('.folder-content ' + CHAT_CONTAINER_SELECTOR).forEach(item => {
            const convoEl = item.querySelector(CHAT_ITEM_SELECTOR);
            const identifier = getIdentifierFromElement(convoEl);

            // Only move if chat shouldn't be in any folder
            if (!identifier || !conversationFolders[identifier] || !folderIds.has(conversationFolders[identifier])) {
                chatListContainer.appendChild(item);
            }
        });

        // Process chats in main container and assign to folders
        Array.from(chatListContainer.children).forEach(itemToMove => {
            const convoEl = itemToMove.querySelector(CHAT_ITEM_SELECTOR);
            const identifier = getIdentifierFromElement(convoEl);
            if (!identifier) return;

            let folderId = conversationFolders[identifier];

            // Clean up references to deleted folders
            if (folderId && !folderIds.has(folderId)) {
                delete conversationFolders[identifier];
                folderId = null;
                dataWasCorrected = true;
            }

            // Move to appropriate folder
            if (folderId) {
                const folderContent = document.querySelector(`.folder[data-folder-id="${folderId}"] .folder-content`);
                if (folderContent && !folderContent.contains(itemToMove)) {
                    folderContent.appendChild(itemToMove);
                }
            }
        });

        if (dataWasCorrected) saveData();
    }

    // --- FOLDER ACTIONS ---
    function createNewFolder() {
        showCustomPromptDialog("Enter New Folder Name", "", "Create", (name) => {
            if (name) {
                const newFolder = { id: `folder_${Date.now()}`, name, color: '#808080', isClosed: false };
                folders.push(newFolder);
                saveData().then(renderFolders);
            }
        });
    }
    // === patch-only: updates an existing folder header in place ===
    function updateFolderHeader(folderId) {
        const folder = folders.find(f => f.id === folderId);
        const folderEl = document.querySelector(`.folder[data-folder-id="${folderId}"]`);
        if (!folder || !folderEl) return;

        folderEl.querySelector('.folder-name').textContent = folder.name;
        folderEl.querySelector('.folder-color-indicator').style.backgroundColor = folder.color;
    }
    function renameFolder(folderId) {
        const folder = folders.find(f => f.id === folderId);
        if (!folder) return;

        showCustomPromptDialog("Rename Folder", folder.name, "Save", (newName) => {
            if (newName && newName !== folder.name) {
                folder.name = newName;
                saveData().then(() => updateFolderHeader(folderId));
            }
        });
    }

    async function deleteFolder(folderId) {
        Object.keys(conversationFolders).forEach(id => {
            if (conversationFolders[id] === folderId) delete conversationFolders[id];
        });
        folders = folders.filter(f => f.id !== folderId);
        await saveData();
        renderFolders();
    }

    function toggleFolder(folderId) {
        const folder = folders.find(f => f.id === folderId);
        if (folder) {
            folder.isClosed = !folder.isClosed;
            const folderEl = document.querySelector(`.folder[data-folder-id="${folderId}"]`);
            if (folderEl) folderEl.classList.toggle('closed');
            saveData();
        }
    }

    // --- CONTEXT MENU & DIALOGS ---
    function showContextMenu(event, folderId) {
        event.preventDefault();
        event.stopPropagation();
        closeContextMenu();

        const btn = event.currentTarget;
        const rect = btn.getBoundingClientRect();

        const menu = document.createElement('div');
        menu.className = 'folder-context-menu';
        menu.id = 'folder-context-menu-active';

        const items = {
            'Rename': () => renameFolder(folderId),
            'Change Color': () => showColorPickerDialog(folderId),
            'Delete Folder': () => showConfirmationDialog("Are you sure you want to delete this folder?", () => deleteFolder(folderId), "Delete", "dialog-btn-delete")
        };

        for (const [text, action] of Object.entries(items)) {
            const itemEl = document.createElement('div');
            itemEl.className = 'folder-context-menu-item';
            if (text === 'Delete Folder') itemEl.classList.add('delete');
            itemEl.textContent = text;
            itemEl.onclick = (e) => {
                e.stopPropagation();
                closeContextMenu();
                action(e);
            };
            menu.appendChild(itemEl);
        }

        document.body.appendChild(menu);
        menu.style.display = 'block';
        menu.style.top = `${rect.bottom + window.scrollY}px`;
        menu.style.left = `${rect.right + window.scrollX - menu.offsetWidth}px`;

        setTimeout(() => document.addEventListener('click', closeContextMenu, { once: true }), 0);
    }

    function closeContextMenu() {
        const menu = document.getElementById('folder-context-menu-active');
        if (menu) menu.remove();
    }

    function showColorPickerDialog(folderId) {
        const folder = folders.find(f => f.id === folderId);
        if (!folder) return;

        const overlay = document.createElement('div');
        overlay.className = 'custom-dialog-overlay';
        const dialogBox = document.createElement('div');
        dialogBox.className = 'custom-dialog-box color-picker-dialog';
        const titleH2 = document.createElement('h2');
        titleH2.textContent = 'Change Folder Color';
        const grid = document.createElement('div');
        grid.className = 'color-picker-grid';

        let selectedColor = folder.color;

        FOLDER_COLORS.forEach(color => {
            const swatch = document.createElement('div');
            swatch.className = 'color-swatch';
            if (color.toLowerCase() === selectedColor.toLowerCase()) swatch.classList.add('selected');
            swatch.style.backgroundColor = color;
            swatch.onclick = () => {
                selectedColor = color;
                hexInput.value = color;
                grid.querySelectorAll('.color-swatch').forEach(s => s.classList.remove('selected'));
                swatch.classList.add('selected');
            };
            grid.appendChild(swatch);
        });

        const hexInput = document.createElement('input');
        hexInput.className = 'custom-dialog-input';
        hexInput.type = 'text';
        hexInput.placeholder = 'Or enter a hex value, e.g. #C0FFEE';
        hexInput.value = selectedColor;

        const btnYes = document.createElement('button');
        btnYes.className = 'custom-dialog-btn dialog-btn-confirm';
        btnYes.textContent = 'Save';
        const btnNo = document.createElement('button');
        btnNo.className = 'custom-dialog-btn dialog-btn-cancel';
        btnNo.textContent = 'Cancel';

        dialogBox.appendChild(titleH2);
        dialogBox.appendChild(grid);
        dialogBox.appendChild(hexInput);
        dialogBox.appendChild(btnYes);
        dialogBox.appendChild(btnNo);
        overlay.appendChild(dialogBox);
        document.body.appendChild(overlay);

        btnYes.onclick = () => {
            const newColor = hexInput.value.trim();
            if (/^#[0-9A-F]{6}$/i.test(newColor)) {
                folder.color = newColor;
                saveData().then(() => updateFolderHeader(folderId));
                overlay.remove();
            } else {
                hexInput.style.border = "1px solid red";
                hexInput.value = "Invalid Hex Code";
                setTimeout(() => {
                    hexInput.style.border = "";
                    hexInput.value = selectedColor;
                }, 2000);
            }
        };
        btnNo.onclick = () => { overlay.remove(); };
    }

    function showConfirmationDialog(message, onConfirm, confirmText = "Confirm", confirmClass = "dialog-btn-confirm") {
        const overlay = document.createElement('div');
        overlay.className = 'custom-dialog-overlay';
        const dialogBox = document.createElement('div');
        dialogBox.className = 'custom-dialog-box';
        const messageP = document.createElement('p');
        messageP.textContent = message;
        const btnYes = document.createElement('button');
        btnYes.className = `custom-dialog-btn ${confirmClass}`;
        btnYes.textContent = confirmText;
        const btnNo = document.createElement('button');
        btnNo.className = 'custom-dialog-btn dialog-btn-cancel';
        btnNo.textContent = 'Cancel';
        dialogBox.appendChild(messageP);
        dialogBox.appendChild(btnYes);
        dialogBox.appendChild(btnNo);
        overlay.appendChild(dialogBox);
        document.body.appendChild(overlay);
        btnYes.onclick = () => { onConfirm(); overlay.remove(); };
        btnNo.onclick = () => { overlay.remove(); };
    }

    function showCustomPromptDialog(title, defaultValue, confirmText, onConfirm) {
        const overlay = document.createElement('div');
        overlay.className = 'custom-dialog-overlay';
        const dialogBox = document.createElement('div');
        dialogBox.className = 'custom-dialog-box';
        const titleH2 = document.createElement('h2');
        titleH2.textContent = title;
        const input = document.createElement('input');
        input.className = 'custom-dialog-input';
        input.type = 'text';
        input.value = defaultValue;
        const btnYes = document.createElement('button');
        btnYes.className = 'custom-dialog-btn dialog-btn-confirm';
        btnYes.textContent = confirmText;
        const btnNo = document.createElement('button');
        btnNo.className = 'custom-dialog-btn dialog-btn-cancel';
        btnNo.textContent = 'Cancel';
        dialogBox.appendChild(titleH2);
        dialogBox.appendChild(input);
        dialogBox.appendChild(btnYes);
        dialogBox.appendChild(btnNo);
        overlay.appendChild(dialogBox);
        document.body.appendChild(overlay);
        input.focus();
        input.select();
        btnYes.onclick = () => { onConfirm(input.value); overlay.remove(); };
        btnNo.onclick = () => { overlay.remove(); };
        input.onkeydown = (e) => { if (e.key === 'Enter') btnYes.click(); };
    }

    // --- DRAG AND DROP ---
    function setupDragAndDrop() {
        const chatListContainer = document.querySelector(CHAT_LIST_CONTAINER_SELECTOR);
        if (!chatListContainer) return;

        new Sortable(chatListContainer, {
            group: 'shared',
            animation: 150,
            onEnd: function() {
                rebuildAndSaveState();
            },
        });

        document.querySelectorAll('.folder-content').forEach(folderContentEl => {
            new Sortable(folderContentEl, {
                group: 'shared',
                animation: 150,
                onEnd: function() {
                    rebuildAndSaveState();
                },
            });
        });
    }

    function rebuildAndSaveState() {
        const newConversationFolders = {};
        document.querySelectorAll('.folder').forEach(folderEl => {
            const folderId = folderEl.dataset.folderId;
            folderEl.querySelectorAll(CHAT_CONTAINER_SELECTOR).forEach(item => {
                const id = getIdentifierFromElement(item.querySelector(CHAT_ITEM_SELECTOR));
                if (id) {
                    newConversationFolders[id] = folderId;
                }
            });
        });
        conversationFolders = newConversationFolders;
        saveData();
    }

    // --- INITIALIZATION ---
    function initialize() {
        const injectionPoint = document.querySelector(INJECTION_POINT_SELECTOR);
        if (!injectionPoint) return false;

        if (chatItemCache.size === 0) {
            const chats = document.querySelectorAll(CHAT_CONTAINER_SELECTOR);
            if (chats.length > 0) {
                chats.forEach(chat => {
                    const id = getIdentifierFromElement(chat.querySelector(CHAT_ITEM_SELECTOR));
                    if (id) chatItemCache.set(id, chat);
                });
            }
        }

        if (document.getElementById('folder-ui-container')) {
            organizeConversations();
            return true;
        }

        const uiContainer = document.createElement('div');
        uiContainer.id = 'folder-ui-container';
        const addButton = document.createElement('button');
        addButton.id = 'add-folder-btn';
        addButton.textContent = '+ New Folder';
        addButton.onclick = createNewFolder;
        const folderContainer = document.createElement('div');
        folderContainer.id = 'folder-container';
        uiContainer.appendChild(addButton);
        uiContainer.appendChild(folderContainer);
        injectionPoint.prepend(uiContainer);
        renderFolders();
        return true;
    }

    // --- MAIN EXECUTION ---
    loadData().then(() => {
        const initInterval = setInterval(() => {
            if (initialize()) {
                clearInterval(initInterval);
            }
        }, 500);
    });

    // --- OPTIONAL RESET BUTTON ---
    function addResetButton() {
        if (document.getElementById('reset-data-btn')) return;
        const resetButton = document.createElement('button');
        resetButton.id = 'reset-data-btn';
        resetButton.textContent = 'Reset Folder Data';
        resetButton.onclick = () => {
            showConfirmationDialog('Are you sure you want to delete all folder data? This cannot be undone.', () => {
                GM_deleteValue('gemini_folders');
                GM_deleteValue('gemini_convo_folders');
                location.reload();
            }, 'Reset', 'dialog-btn-delete');
        };
        document.body.appendChild(resetButton);
    }
    addResetButton();
    // --- END OPTIONAL RESET BUTTON ---
/******************************************************************
 *  DEBUG INSTRUMENTATION – add right above the final “})();”
 ******************************************************************/

// ---- Clipboard helper (works in all modern browsers) ----------
async function copyTextToClipboard(text) {
    try {
        await navigator.clipboard.writeText(text);
        console.info('[Gemini Folder Debug] Copied to clipboard');
    } catch (err) {
        console.warn('[Gemini Folder Debug] Clipboard write failed:', err);
    }
}

// ---- Collect full runtime + DOM state -------------------------
function collectDebugState() {
    const stateSnapshot = {
        storedFolders: folders,                       // what’s in memory
        storedConversationFolders: conversationFolders,
        domFolders: {},                               // what’s actually in the DOM
        domOrphans: []                                // chats not in any folder
    };

    // Map DOM placements
    document.querySelectorAll('.folder').forEach(folderEl => {
        const folderId = folderEl.dataset.folderId;
        stateSnapshot.domFolders[folderId] = [];
        folderEl.querySelectorAll('div[data-test-id="conversation"]').forEach(chatEl => {
            const id = getIdentifierFromElement(chatEl);
            if (id) stateSnapshot.domFolders[folderId].push(id);
        });
    });

    // Chats still living in the main container
    const mainContainer = document.querySelector(CHAT_LIST_CONTAINER_SELECTOR);
    if (mainContainer) {
        mainContainer.querySelectorAll('div[data-test-id="conversation"]').forEach(chatEl => {
            const id = getIdentifierFromElement(chatEl);
            if (id) stateSnapshot.domOrphans.push(id);
        });
    }

    return stateSnapshot;
}

// ---- Add floating button to UI --------------------------------
function addDebugButton() {
    if (document.getElementById('gemini-folder-debug-btn')) return;

    const btn = document.createElement('button');
    btn.id = 'gemini-folder-debug-btn';
    btn.textContent = '🩺 Copy Debug Info';
    Object.assign(btn.style, {
        position: 'fixed',
        bottom: '15px',
        left:  '15px',
        zIndex:  10000,
        padding: '8px 12px',
        fontSize: '13px',
        background: '#d32f2f',
        color: '#fff',
        border: '2px solid #fff',
        borderRadius: '6px',
        cursor: 'pointer'
    });

    btn.onclick = async () => {
        const snapshot = collectDebugState();
        const json = JSON.stringify(snapshot, null, 2);
        console.log('%c[Gemini Folder Debug] Snapshot below:', 'color:#d32f2f;font-weight:bold;');
        console.log(json);
        await copyTextToClipboard(json);
        btn.textContent = '✅ Copied!';
        setTimeout(() => { btn.textContent = '🩺 Copy Debug Info'; }, 2000);
    };

    document.body.appendChild(btn);
}

// run once UI is present
const debugInitInterval = setInterval(() => {
    if (document.querySelector(INJECTION_POINT_SELECTOR)) {
        addDebugButton();
        clearInterval(debugInitInterval);
    }
}, 600);

/******************************************************************
 *  END DEBUG INSTRUMENTATION
 ******************************************************************/
    /******************************************************************
 *  Header row (Folders  +  ⚙︎) – added without disturbing core  *
 ******************************************************************/

(function addHeaderOnce () {
  // Wait until the original UI container exists
  const check = setInterval(() => {
    const ui  = document.querySelector('#folder-ui-container');
    const add = ui?.querySelector('button, input[value="+ New Folder"]'); // old add-folder button
    if (!ui || !add) return;

    clearInterval(check);

    /* Hide the old UI bits ------------------------------------ */
    add.style.display = 'none';                    // old “+ New Folder” button
    document.querySelectorAll('.debug-btn, .reset-btn')
            .forEach(btn => btn.style.display = 'none'); // bottom red buttons

    /* Build header row --------------------------------------- */
    const header = document.createElement('div');
    header.id = 'folders-header-inline';
    header.style.cssText = 'display:flex;align-items:center;margin:8px 0 12px;font-family:Roboto,Arial,sans-serif;';

    const title = Object.assign(document.createElement('span'), { textContent: 'Folders', style: 'font-weight:600;' });
    const spacer = Object.assign(document.createElement('span'), { style: 'flex:1;' });

    const btnCSS = 'background:none;border:none;font-size:18px;cursor:pointer;width:28px;height:28px;border-radius:4px;display:flex;align-items:center;justify-content:center;';
    const plus  = Object.assign(document.createElement('button'), { textContent:'+', title:'New Folder', style:btnCSS });
    const gear  = Object.assign(document.createElement('button'), { textContent:'⚙︎', title:'Tools',      style:btnCSS });

    /* Re-use the existing functions already in the page */
    plus.onclick = () => add.click();                 // create-folder dialog
    gear.onclick = showToolsPop;                      // small pop-over

    header.append(title, spacer, plus, gear);
    ui.prepend(header);

    /* ---------- tiny pop-over ---------- */
    function showToolsPop (e) {
      e.stopPropagation();
      const old = document.getElementById('folder-tools-pop-inline');
      if (old) return old.remove();

      const pop = document.createElement('div');
      pop.id = 'folder-tools-pop-inline';
      pop.style.cssText = 'position:absolute;z-index:10000;background:#333;color:#fff;border:1px solid #555;border-radius:8px;padding:6px;font:14px Roboto,Arial;';
      const { bottom, right } = e.currentTarget.getBoundingClientRect();
      pop.style.top  = `${bottom + window.scrollY}px`;
      pop.style.left = `${right  + window.scrollX - 150}px`;
      pop.style.minWidth = '150px';

      const item = (txt, fn) => {
        const b = Object.assign(document.createElement('button'), { textContent:txt });
        b.style.cssText = 'display:block;width:100%;background:none;border:none;color:#fff;padding:8px 14px;text-align:left;border-radius:6px;cursor:pointer;';
        b.onmouseover = () => b.style.background = '#555';
        b.onmouseout  = () => b.style.background = '';
        b.onclick = fn;
        pop.appendChild(b);
      };

      item('Copy Debug Code', async () => {
        if (window.collectDebugState && navigator.clipboard)
          await navigator.clipboard.writeText(JSON.stringify(window.collectDebugState(), null, 2));
        pop.remove();
      });

      item('Reset Folder Data', () => {
        if (confirm('Delete all folder data?')) {
          GM_deleteValue('gemini_folders');
          GM_deleteValue('gemini_convo_folders');
          location.reload();
        }
      });

      document.body.appendChild(pop);
      setTimeout(() => document.addEventListener('click', () => pop.remove(), { once:true }), 0);
    }
  }, 300);
})();

})();