@@ -262,12 +262,15 @@ def apply_tool_visibility(
262262 .group { background: var(--surface); border-radius: 12px; margin-bottom: 8px;
263263 overflow: hidden; border: 1px solid var(--border); }
264264 .group-header { display: flex; align-items: center; justify-content: space-between;
265- padding: 12px 16px; cursor: pointer; user-select: none; }
265+ padding: 12px 16px; cursor: pointer; user-select: none; gap: 12px; }
266266 .group-header:hover { background: var(--surface-hover); }
267+ .group-header-left { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0; }
267268 .group-name { font-weight: 600; font-size: 0.95rem; }
268- .group-count { font-size: 0.8rem; color: var(--text-secondary); margin-left: 8px; }
269- .group-chevron { transition: transform 0.2s; color: var(--text-secondary); }
269+ .group-count { font-size: 0.8rem; color: var(--text-secondary); }
270+ .group-chevron { transition: transform 0.2s; color: var(--text-secondary);
271+ display: inline-block; width: 12px; }
270272 .group-chevron.open { transform: rotate(90deg); }
273+ .group-master { flex-shrink: 0; }
271274 .group-tools { display: none; border-top: 1px solid var(--border); }
272275 .group-tools.open { display: block; }
273276 .tool { display: flex; align-items: center; justify-content: space-between;
@@ -328,6 +331,7 @@ def apply_tool_visibility(
328331let toolData = [];
329332let toolStates = {};
330333let saveTimer = null;
334+ let openGroups = new Set();
331335
332336async function loadTools() {
333337 const resp = await fetch('./api/settings/tools');
@@ -364,21 +368,62 @@ def apply_tool_visibility(
364368 const group = document.createElement('div');
365369 group.className = 'group';
366370
371+ // Per-group toggle state: enabled if ANY non-mandatory/non-gated tool is enabled
372+ const toggleable = tools.filter(t => !MANDATORY.includes(t.name) && !t.disabled_by);
373+ const anyEnabled = toggleable.some(t => getState(t.name) !== 'disabled');
374+ const groupEnabled = tools.filter(t => {
375+ const s = getState(t.name);
376+ return MANDATORY.includes(t.name) || (!t.disabled_by && s !== 'disabled');
377+ }).length;
378+
367379 const header = document.createElement('div');
368380 header.className = 'group-header';
369- const groupEnabled = tools.filter(t => getState(t.name) !== 'disabled').length;
370- header.innerHTML = `<div><span class="group-name">${tag}</span>` +
371- `<span class="group-count">${groupEnabled}/${tools.length} enabled</span></div>` +
372- `<span class="group-chevron">▶</span>`;
373- header.onclick = () => {
381+ header.innerHTML = `<div class="group-header-left">` +
382+ `<span class="group-chevron">▶</span>` +
383+ `<span class="group-name">${tag}</span>` +
384+ `<span class="group-count">${groupEnabled}/${tools.length} enabled</span>` +
385+ `</div>` +
386+ `<label class="switch group-master" title="Enable/disable all tools in this group">` +
387+ `<input type="checkbox" ${anyEnabled ? 'checked' : ''} ${toggleable.length === 0 ? 'disabled' : ''}>` +
388+ `<span class="slider"></span>` +
389+ `</label>`;
390+
391+ const chevron = header.querySelector('.group-chevron');
392+ const masterInput = header.querySelector('.group-master input');
393+
394+ header.addEventListener('click', (e) => {
395+ // Ignore clicks on the master toggle itself
396+ if (e.target.closest('.group-master')) return;
397+ if (openGroups.has(tag)) openGroups.delete(tag);
398+ else openGroups.add(tag);
374399 const toolsDiv = group.querySelector('.group-tools');
375- const chevron = header.querySelector('.group-chevron');
376400 toolsDiv.classList.toggle('open');
377401 chevron.classList.toggle('open');
378- };
402+ });
403+
404+ if (masterInput) {
405+ masterInput.addEventListener('click', (e) => e.stopPropagation());
406+ masterInput.addEventListener('change', (e) => {
407+ const target = e.target.checked ? 'enabled' : 'disabled';
408+ toggleable.forEach(t => {
409+ if (target === 'enabled') {
410+ // Restore to pinned if it was pinned by default, else enabled
411+ toolStates[t.name] = DEFAULT_PINNED.includes(t.name) ? 'pinned' : 'enabled';
412+ } else {
413+ toolStates[t.name] = 'disabled';
414+ }
415+ });
416+ scheduleSave();
417+ render();
418+ });
419+ }
379420
380421 const toolsDiv = document.createElement('div');
381422 toolsDiv.className = 'group-tools';
423+ if (openGroups.has(tag)) {
424+ toolsDiv.classList.add('open');
425+ chevron.classList.add('open');
426+ }
382427
383428 tools.forEach(t => {
384429 const state = getState(t.name);
0 commit comments