diff --git a/web/index.js b/web/index.js
index 46d349d..c8ad863 100644
--- a/web/index.js
+++ b/web/index.js
@@ -7,7 +7,7 @@ import { app } from "../../scripts/app.js";
// Bump on every release. Exposed on window so it's trivial to check in the
// console (`window.__cnv_version`) whether the browser is on the latest JS
// or still serving a cached older copy.
-const CNV_VERSION = "0.0.6";
+const CNV_VERSION = "0.1.0";
try {
window.__cnv_version = CNV_VERSION;
console.info(`[comfyui-navigator] loaded v${CNV_VERSION}`);
@@ -220,6 +220,25 @@ function findMuterOwningGroup(targetTitle) {
return null;
}
+function getMuterWidgetForGroup(title) {
+ const owner = findMuterOwningGroup(title);
+ return owner ? owner.widgets.get(title) : null;
+}
+
+function isGroupEnabled(title) {
+ const w = getMuterWidgetForGroup(title);
+ return w ? !!w?.value?.toggled : null; // null = no muter manages it
+}
+
+function setGroupEnabled(title, on) {
+ const w = getMuterWidgetForGroup(title);
+ if (!w) return false;
+ setMuterWidget(w, on);
+ const owner = findMuterOwningGroup(title);
+ owner?.muter?.setDirtyCanvas?.(true, true);
+ return true;
+}
+
function setMuterWidget(widget, toggled) {
// Value is { toggled: bool }. Mutate in place AND call callback so rgthree
// re-renders + dispatches its own change handlers.
@@ -438,25 +457,77 @@ function renderList() {
const row = document.createElement("div");
row.className = "cnv-row";
row.style.setProperty("--cnv-color", groupColorOrDefault(g));
+ row.dataset.title = title;
+
+ const enabled = isGroupEnabled(title);
+ const showCheckbox = enabled !== null; // null = no muter manages this group
+
row.innerHTML = `
+ ${showCheckbox ? `` : ``}
${i < 9 ? `${i + 1}` : ""}
+
+
`;
row.querySelector(".cnv-row__title").textContent = title;
+
+ // Checkbox: toggle muter widget (don't jump)
+ const cb = row.querySelector("input.cnv-row__cb");
+ if (cb) {
+ cb.addEventListener("click", (e) => e.stopPropagation());
+ cb.addEventListener("change", (e) => {
+ setGroupEnabled(title, cb.checked);
+ });
+ }
+
+ // Click the row body (anywhere except checkbox/jump/menu) → jump
row.addEventListener("click", (e) => {
if (e.button !== 0) return;
+ if (e.target.closest(".cnv-row__cb, .cnv-row__menu")) return;
jumpToGroup(g);
});
+
+ // Explicit jump button (visible affordance)
+ row.querySelector(".cnv-row__jump").addEventListener("click", (e) => {
+ e.stopPropagation();
+ jumpToGroup(g);
+ });
+
+ // Three-dot menu
+ row.querySelector(".cnv-row__menu").addEventListener("click", (e) => {
+ e.stopPropagation();
+ e.preventDefault();
+ openRowMenu(g, e);
+ });
+
+ // Right-click anywhere on row also opens menu
row.addEventListener("contextmenu", (e) => {
e.preventDefault();
e.stopPropagation();
openRowMenu(g, e);
});
+
state.listEl.appendChild(row);
}
}
+/** Cheap re-sync of just the checkbox states without rebuilding the list.
+ * Catches the case where the user toggles a widget directly on the muter
+ * node (or via rgthree's keyboard shortcuts) and we need to reflect it in
+ * the panel. */
+function syncCheckboxStates() {
+ if (!state.listEl) return;
+ for (const row of state.listEl.querySelectorAll(".cnv-row")) {
+ const title = row.dataset.title;
+ const cb = row.querySelector("input.cnv-row__cb");
+ if (!cb) continue;
+ const enabled = isGroupEnabled(title);
+ if (enabled === null) continue;
+ if (cb.checked !== enabled) cb.checked = enabled;
+ }
+}
+
// --- keyboard hotkeys ---------------------------------------------------
function isTypingTarget(el) {
@@ -509,15 +580,18 @@ app.registerExtension({
// Catch graph swaps (loading a different workflow). ComfyUI fires
// a custom "graphConfigured" or similar; polling _groups length as
- // a cheap fallback.
+ // a cheap fallback. Also re-sync checkbox states so that toggles
+ // made directly on the muter node propagate to the panel UI.
let lastGroupCount = -1;
setInterval(() => {
const n = (app.graph?._groups?.length || 0);
if (n !== lastGroupCount) {
lastGroupCount = n;
scheduleRender();
+ } else {
+ syncCheckboxStates();
}
- }, 800);
+ }, 600);
},
async loadedGraphNode() {
scheduleRender();
diff --git a/web/styles.css b/web/styles.css
index 6e90c52..c2e1f10 100644
--- a/web/styles.css
+++ b/web/styles.css
@@ -85,8 +85,8 @@
.cnv-row {
display: flex;
align-items: center;
- gap: 8px;
- padding: 6px 8px;
+ gap: 6px;
+ padding: 4px 6px;
border-radius: 6px;
cursor: pointer;
border-left: 3px solid var(--cnv-color, #3b82f6);
@@ -94,9 +94,26 @@
.cnv-row:hover { background: rgba(255, 255, 255, 0.06); }
.cnv-row--active { background: rgba(96, 165, 250, 0.14); }
+.cnv-row__cb {
+ width: 14px;
+ height: 14px;
+ margin: 0;
+ cursor: pointer;
+ accent-color: var(--cnv-color, #3b82f6);
+ flex-shrink: 0;
+}
+.cnv-row__cb--placeholder {
+ display: inline-block;
+ width: 14px;
+ height: 14px;
+ border-radius: 3px;
+ border: 1px dashed rgba(255, 255, 255, 0.15);
+ background: transparent;
+}
+
.cnv-row__dot {
- width: 8px;
- height: 8px;
+ width: 7px;
+ height: 7px;
border-radius: 50%;
background: var(--cnv-color, #3b82f6);
flex-shrink: 0;
@@ -118,5 +135,22 @@
font-family: ui-monospace, monospace;
}
+.cnv-row__jump,
+.cnv-row__menu {
+ background: transparent;
+ border: none;
+ color: #94a3b8;
+ cursor: pointer;
+ padding: 1px 4px;
+ border-radius: 4px;
+ font-size: 13px;
+ line-height: 1;
+}
+.cnv-row__jump:hover,
+.cnv-row__menu:hover {
+ background: rgba(255, 255, 255, 0.1);
+ color: #e5e7eb;
+}
+
.cnv-panel--collapsed .cnv-panel__search-wrap,
.cnv-panel--collapsed .cnv-panel__list { display: none; }