1714→                        var already = contextThreads.some(function(ct) { return ct.threadId === t.thread_id; });
  1715→                        var isCurrent = t.thread_id === currentThreadId;
  1716→                        html += '
';
  1717→                        html += '
';
  1718→                        html += '' + escapeHtml(t.subject || '(No Subject)') + '';
  1719→                        html += '
' + (t.email_count || 0) + ' emails
';
  1720→                        html += '
';
  1721→                        if (isCurrent) {
  1722→                            html += 'Current';
  1723→                        } else if (already) {
  1724→                            html += 'Added';
  1725→                        } else {
  1726→                            html += '<button class="context-add-btn" onclick="addThreadToContext(\'' + t.thread_id + '\', \'' + escapeHtml(t.subject || '').replace(/'/g, "\\'") + '\', ' + (t.email_count || 0) + ')">+ Add</button>';
  1727→                        }
  1728→                        html += '
';
  1729→                    });
  1730→
  1731→                    resultsEl.innerHTML = html;
  1732→
  1733→                    // Also load contact memories
  1734→                    loadContactMemories(contactId);
  1735→                });
  1736→        }
  1737→
  1738→        function addAllContactThreads() {
  1739→            var threads = window._lastContactThreads || [];
  1740→            threads.forEach(function(t) {
  1741→                if (t.thread_id !== currentThreadId && !contextThreads.some(function(ct) { return ct.threadId === t.thread_id; })) {
  1742→                    contextThreads.push({
  1743→                        threadId: t.thread_id,
  1744→                        subject: t.subject || '(No Subject)',
  1745→                        emailCount: t.email_count || 0
  1746→                    });
  1747→                }
  1748→            });
  1749→            updateContextCart();
  1750→            updateTokenEstimate();
  1751→            // Refresh search results to show "Added" labels
  1752→            var q = document.getElementById('context-search').value.trim();
  1753→            if (q) searchContext(q);
  1754→        }
  1755→
  1756→        function addThreadToContext(threadId, subject, emailCount) {
  1757→            if (threadId === currentThreadId) return;
  1758→            if (contextThreads.some(function(ct) { return ct.threadId === threadId; })) return;
  1759→            contextThreads.push({
  1760→                threadId: threadId,
  1761→                subject: subject,
  1762→                emailCount: emailCount
  1763→            });
  1764→            updateContextCart();
  1765→            updateTokenEstimate();
  1766→            // Refresh search results
  1767→            var q = document.getElementById('context-search').value.trim();
  1768→            if (q) searchContext(q);
  1769→        }
  1770→
  1771→        function removeThreadFromContext(threadId) {
  1772→            contextThreads = contextThreads.filter(function(ct) { return ct.threadId !== threadId; });
  1773→            updateContextCart();
  1774→            updateTokenEstimate();
  1775→        }
  1776→
  1777→        function updateContextCart() {
  1778→            var el = document.getElementById('context-cart');
  1779→            if (contextThreads.length === 0) {
  1780→                el.innerHTML = '';
  1781→                return;
  1782→            }
  1783→            var totalEmails = contextThreads.reduce(function(sum, ct) { return sum + ct.emailCount; }, 0);
  1784→            var html = '
';
  1785→            html += '
' + contextThreads.length + ' additional thread(s), ~' + totalEmails + ' emails
';
  1786→            contextThreads.forEach(function(ct) {
  1787→                html += '
';
  1788→                html += '' + escapeHtml(ct.subject) + ' (' + ct.emailCount + ')';
  1789→                html += '<button class="context-cart-remove" onclick="removeThreadFromContext(\'' + ct.threadId + '\')">×</button>';
  1790→                html += '
';
  1791→            });
  1792→            html += '
';
  1793→            el.innerHTML = html;

var already = contextThreads.some(function(ct) { return ct.threadId === t.thread_id; });
                        var isCurrent = t.thread_id === currentThreadId;
                        html += '
';
                        html += '
';
                        html += '' + escapeHtml(t.subject || '(No Subject)') + '';
                        html += '
' + (t.email_count || 0) + ' emails
';
                        html += '
';
                        if (isCurrent) {
                            html += 'Current';
                        } else if (already) {
                            html += 'Added';
                        } else {
                            html += '<button class="context-add-btn" onclick="addThreadToContext(\'' + t.thread_id + '\', \'' + escapeHtml(t.subject || '').replace(/'/g, "\\'") + '\', ' + (t.email_count || 0) + ')">+ Add</button>';
                        }
                        html += '
';
                    });

                    resultsEl.innerHTML = html;

                    // Also load contact memories
                    loadContactMemories(contactId);
                });
        }

        function addAllContactThreads() {
            var threads = window._lastContactThreads || [];
            threads.forEach(function(t) {
                if (t.thread_id !== currentThreadId && !contextThreads.some(function(ct) { return ct.threadId === t.thread_id; })) {
                    contextThreads.push({
                        threadId: t.thread_id,
                        subject: t.subject || '(No Subject)',
                        emailCount: t.email_count || 0
                    });
                }
            });
            updateContextCart();
            updateTokenEstimate();
            // Refresh search results to show "Added" labels
            var q = document.getElementById('context-search').value.trim();
            if (q) searchContext(q);
        }

        function addThreadToContext(threadId, subject, emailCount) {
            if (threadId === currentThreadId) return;
            if (contextThreads.some(function(ct) { return ct.threadId === threadId; })) return;
            contextThreads.push({
                threadId: threadId,
                subject: subject,
                emailCount: emailCount
            });
            updateContextCart();
            updateTokenEstimate();
            // Refresh search results
            var q = document.getElementById('context-search').value.trim();
            if (q) searchContext(q);
        }

        function removeThreadFromContext(threadId) {
            contextThreads = contextThreads.filter(function(ct) { return ct.threadId !== threadId; });
            updateContextCart();
            updateTokenEstimate();
        }

        function updateContextCart() {
            var el = document.getElementById('context-cart');
            if (contextThreads.length === 0) {
                el.innerHTML = '';
                return;
            }
            var totalEmails = contextThreads.reduce(function(sum, ct) { return sum + ct.emailCount; }, 0);
            var html = '
';
            html += '
' + contextThreads.length + ' additional thread(s), ~' + totalEmails + ' emails
';
            contextThreads.forEach(function(ct) {
                html += '
';
                html += '' + escapeHtml(ct.subject) + ' (' + ct.emailCount + ')';
                html += '<button class="context-cart-remove" onclick="removeThreadFromContext(\'' + ct.threadId + '\')">×</button>';
                html += '
';
            });
            html += '
';
            el.innerHTML = html;


Now I see the issues:

1. **Thread search in context builder** (`searchContext` function, line 1646) — searches with `limit=5`, so only 5 threads are shown. When searching "delta-engineering.be", the threads API now searches from/to emails too (after my last fix), but only 5 are returned.

2. **Contact threads** (`loadContactThreads` function, line 1700) — loads with `limit=20`, so at most 20 threads per contact.

3. There's no "Add All" button for thread search results — only for contact threads (line 1707).

The user wants:

1. Return ALL matching threads when searching by domain (not just 5)
2. An easy "Add All" button to add all results at once

I'll fix this by bumping up the thread search limit to something like 100 or 200 to capture more results, and then add an "Add All Threads" button to the search results UI similar to what exists for contact threads.

I need to store the search results so the "Add All" function can access them, then create a new function that adds all found threads at once. I should also increase the contact threads limit from 20 to something higher. Though I'm realizing that loading 296 threads for delta-engineering.be as context would be massive and hit token limits, so the UI should show all results but let the token estimator warn users if they're going overboard.


I see the problems. The context builder search has two issues:

1. **Thread search limit is 5** (line 1646) — so you only see 5 of the 296 matching threads
2. **No "Add All" button** for thread search results (only exists for contact thread lists)

Let me fix both — increase the limit and add an "Add All" button for thread search results.


1639→        function searchContext(query) {
  1640→            var resultsEl = document.getElementById('context-search-results');
  1641→            resultsEl.innerHTML = '
Searching...
';
  1642→
  1643→            // Search contacts and threads in parallel
  1644→            Promise.all([
  1645→                fetch('api.php?action=contacts&search=' + encodeURIComponent(query) + '&limit=5').then(function(r) { return r.json(); }),
  1646→                fetch('api.php?action=threads&search=' + encodeURIComponent(query) + '&limit=5').then(function(r) { return r.json(); })
  1647→            ]).then(function(results) {
  1648→                var contacts = results[0].contacts || [];
  1649→                var threads = results[1].threads || [];
  1650→                var html = '';
  1651→
  1652→                if (contacts.length > 0) {
  1653→                    html += '
CONTACTS
';
  1654→                    contacts.forEach(function(c) {
  1655→                        html += '
';
  1656→                        html += '
';
  1657→                        html += '' + escapeHtml(c.name || c.email) + '';
  1658→                        html += '
' + escapeHtml(c.email || '') + ' | ' + (c.email_count || 0) + ' emails
';
  1659→                        html += '
';
  1660→                        html += '<button class="context-add-btn" onclick="loadContactThreads(' + (c.id || c.contact_id) + ', \'' + escapeHtml(c.name || '') + '\')">View Threads</button>';
  1661→                        html += '
';
  1662→                    });
  1663→                }
  1664→
  1665→                if (threads.length > 0) {
  1666→                    html += '
THREADS
';
  1667→                    threads.forEach(function(t) {
  1668→                        var already = contextThreads.some(function(ct) { return ct.threadId === t.thread_id; });
  1669→                        var isCurrent = t.thread_id === currentThreadId;
  1670→                        html += '
';
  1671→                        html += '
';
  1672→                        html += '' + escapeHtml(t.subject || '(No Subject)') + '';
  1673→                        html += '
' + (t.email_count || 0) + ' emails | ' + escapeHtml(t.contact_name || '') + '
';
  1674→                        html += '
';
  1675→                        if (isCurrent) {
  1676→                            html += 'Current';
  1677→                        } else if (already) {
  1678→                            html += 'Added';
  1679→                        } else {
  1680→                            html += '<button class="context-add-btn" onclick="addThreadToContext(\'' + t.thread_id + '\', \'' + escapeHtml(t.subject || '').replace(/'/g, "\\'") + '\', ' + (t.email_count || 0) + ')">+ Add</button>';
  1681→                        }
  1682→                        html += '
';
  1683→                    });
  1684→                }
  1685→
  1686→                if (!html) {
  1687→                    html = '
No results found
';
  1688→                }
  1689→
  1690→                resultsEl.innerHTML = html;
  1691→            }).catch(function(err) {
  1692→                resultsEl.innerHTML = '
Search failed
';
  1693→            });
  1694→        }

function searchContext(query) {
            var resultsEl = document.getElementById('context-search-results');
            resultsEl.innerHTML = '
Searching...
';

            // Search contacts and threads in parallel
            Promise.all([
                fetch('api.php?action=contacts&search=' + encodeURIComponent(query) + '&limit=5').then(function(r) { return r.json(); }),
                fetch('api.php?action=threads&search=' + encodeURIComponent(query) + '&limit=5').then(function(r) { return r.json(); })
            ]).then(function(results) {
                var contacts = results[0].contacts || [];
                var threads = results[1].threads || [];
                var html = '';

                if (contacts.length > 0) {
                    html += '
CONTACTS
';
                    contacts.forEach(function(c) {
                        html += '
';
                        html += '
';
                        html += '' + escapeHtml(c.name || c.email) + '';
                        html += '
' + escapeHtml(c.email || '') + ' | ' + (c.email_count || 0) + ' emails
';
                        html += '
';
                        html += '<button class="context-add-btn" onclick="loadContactThreads(' + (c.id || c.contact_id) + ', \'' + escapeHtml(c.name || '') + '\')">View Threads</button>';
                        html += '
';
                    });
                }

                if (threads.length > 0) {
                    html += '
THREADS
';
                    threads.forEach(function(t) {
                        var already = contextThreads.some(function(ct) { return ct.threadId === t.thread_id; });
                        var isCurrent = t.thread_id === currentThreadId;
                        html += '
';
                        html += '
';
                        html += '' + escapeHtml(t.subject || '(No Subject)') + '';
                        html += '
' + (t.email_count || 0) + ' emails | ' + escapeHtml(t.contact_name || '') + '
';
                        html += '
';
                        if (isCurrent) {
                            html += 'Current';
                        } else if (already) {
                            html += 'Added';
                        } else {
                            html += '<button class="context-add-btn" onclick="addThreadToContext(\'' + t.thread_id + '\', \'' + escapeHtml(t.subject || '').replace(/'/g, "\\'") + '\', ' + (t.email_count || 0) + ')">+ Add</button>';
                        }
                        html += '
';
                    });
                }

                if (!html) {
                    html = '
No results found
';
                }

                resultsEl.innerHTML = html;
            }).catch(function(err) {
                resultsEl.innerHTML = '
Search failed
';
            });
        }


Now let me also check the CSS for the search results container to see if it has a scroll height:


   876→        #context-search {
   877→            width: 100%;
   878→            padding: 10px 12px;
   879→            border: 1px solid var(--border-color);
   880→            border-radius: 8px;
   881→            font-size: 0.9em;
   882→            box-sizing: border-box;
   883→            margin-bottom: 10px;
   884→        }
   885→
   886→        #context-search:focus {
   887→            outline: none;
   888→            border-color: var(--accent-primary);
   889→        }
   890→
   891→        .context-search-results {
   892→            max-height: 250px;
   893→            overflow-y: auto;
   894→        }
   895→

        #context-search {
            width: 100%;
            padding: 10px 12px;
            border: 1px solid var(--border-color);
            border-radius: 8px;
            font-size: 0.9em;
            box-sizing: border-box;
            margin-bottom: 10px;
        }

        #context-search:focus {
            outline: none;
            border-color: var(--accent-primary);
        }

        .context-search-results {
            max-height: 250px;
            overflow-y: auto;
        }


Good, it already has `max-height: 250px` with scroll. But with potentially hundreds of results, I should increase that a bit and make the results area more usable. Now let me apply all the changes:


Edit

        .context-search-results {
            max-height: 250px;
            overflow-y: auto;
        }

        .context-search-results {
            max-height: 400px;
            overflow-y: auto;
        }