Your comments

actually, the changes to the trees don't persist at all! the python script above is probably a better bet.

CORRECTED VERSION 

@Vladyslav are you able to remove the previous version? I can't.

function removeDups() {
    // if DON'T do this, could end up deleting whole subtrees when you only mean to delete the node
    $('#expandAllButton').click();
    urls_list = [];
    urls_set = new Set();
    tonodes = $$('#savedtabundefined');
    tonodes.forEach( (node) => {
        let url = $('.nodeTitleContainer',node).href;
        urls_list.push(url);
        urls_set.add(url);
    });
    console.log(tonodes.length + " tabsoutliner tabs");
    console.log(urls_list.length + " url occurrences");
    console.log(urls_set.size + " distinct urls");
    if(tonodes.length == urls_list.length) {    
        removed_dups = 0;
        kept = 0;    
        dups_with_children = 0
        // keep the last occurrence of a url, and remove the rest 
        last_occurrences = new Map();
        for(let i=urls_list.length-1; i>=0; i--) {
            let url = urls_list[i];
            if( last_occurrences.has(url) ) {
                if( $$('ul>li',tonodes[i]).length == 0 ) {
                    removed_dups++;
                    // tonodes[i].remove();
                }
                else {
                    dups_with_children++;
                    kept++;
                }
                // 
            }        
            else {
                kept++;
                last_occurrences.set(url,i);
            }
        }
        console.log(dups_with_children + " duplicates with children were NOT removed.");
        console.log(removed_dups + " duplicates with no children were removed.");
        console.log(kept + " tabsoutliner tabs were kept.");
    }
    else {
        console.error("Expected number of tabsoutliner nodes to be equal to number of url occurrences. This was false, so stopping to be safe.");
    }
    // undoes expand all
    $('#expandAllButton').click();
}
removeDups();

I'm sorry! There might be a race condition where the expand all button (which I thought would prevent that) doesn't work fast enough. 

I'll just modify it to never delete tabs with children. 

Only removes duplicates based on exactly matching URLs. Keeps the most recent occurrence. 

Here's a script to remove duplicates. There's also a list where you can put strings that, when found in the title of the tab, will cause it to be deleted, regardless of whether there are duplicates.


USAGE:

Bring TabsOutliner to the front.

control+shift+j (windows/linux) or command+shift+j (mac) to open the Developer Tools.

Click "Console"

Copy paste the code below into the line with the cursor. Press enter.


// if DON'T do this, could end up deleting whole subtrees when you only mean to delete the node
$('#expandAllButton').click();
first_occurrences = new Map();
urls_list = [];
urls_set = new Set();
tonodes = $$('#savedtabundefined');
tonodes.forEach( (node) => {
    let url = $('.nodeTitleContainer',node).href;
    urls_list.push(url);
    urls_set.add(url);
});
console.log(tonodes.length + " tabsoutliner tabs");
console.log(urls_list.length + " url occurrences");
console.log(urls_set.size + " distinct urls");
if(tonodes.length == urls_list.length) {    
    removed_dups = 0;
    kept = 0;    
    // keep the last occurrence of a url, and remove the rest 
    for(let i=urls_list.length-1; i>=0; i--) {
        let url = urls_list[i];
        if( first_occurrences.has(url) ) {
            removed_dups++;        
            tonodes[i].remove();
        }        
        else {
            kept++;
            first_occurrences.set(url,i);
        }
    }
    console.log("Removed " + removed_dups + " duplicates.") 
    console.log("Kept " + kept + " tabsoutliner tabs.");
}
else {
    console.error("Expected number of tabsoutliner nodes to be equal to number of url occurrences. This was false, so stopping to be safe.");
}
// undoes expand all
$('#expandAllButton').click();
Thanks for the fast reply. I would've followed up sooner, but the email notification got marked "not important" by gmail.

If I add a copy of the DOM for your hover delete button in the appropriate place programmatically, and make a click event on the button, will the corresponding node in persistent storage be deleted? I realize I could test this in the console, but without ids on the nodes I don't see how to do so easily.
Vladyslav, if you could describe the data structure you use to me, I would be happy to implement this, and you would own the (admittedly small) contribution. The precise, conservative behavior I want is this:

Click a new button, or perhaps a context menu item. I can show you how to easily enable a custom context menu using a lightweight jquery widget, if you're interested.
TO perhaps first warns how many tabs will be deleted with a simple ok/cancel popup.
For each distinct URL x that appears in the current session tree:
Delete every occurrence of x in a "crashed" or unnamed window before the most-recent occurrence. If a duplicate appears in a named window, leave it alone.
Glad to learn this feature is available. I've used your excellent extension for years and I've wanted the feature for a long time.

Here is a use case that's consistent with your suggested workflow: I have _exactly_ the 30 tabs loaded that you want (and, say, 70 others that are saved/closed), but I need to restart your browser or computer to install an update. This saves me from 60 clicks to reopen each of those tabs. More importantly, perhaps, it provides the psychological reassurance that I can close my browser at no cost, which I do benefit from if I'm doing something else RAM-intensive. This may be more of a problem on Macs; the OS tends to use extra RAM to perform background maintenance tasks, so even if _I'm_ only using 6GB, 7.95GB is tied up, which means the OS will have to write to disk when I launch a new memory-intensive application.