34 Commits
Author SHA1 Message Date
gregowahooandClaude Opus 4.8 43db7b0d25 feat: double-click row toggles group enabled
Double-clicking a group row flips its enabled state via the rgthree
Fast Groups Muter (off->on, on->off), with the pill label and row
highlight updated immediately. No-op for placeholder rows with no muter.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 10:57:49 -05:00
Greg Ohlsen f0e9e29c85 Add additional image to README 2026-06-05 13:49:11 -05:00
gregowahoo 0adad79f6a docs: remove duplicate panel image and improve alt text 2026-06-05 13:19:58 -05:00
gregowahoo 0ede2b8926 docs: add row-click navigation demo to Usage section v0.4.3 2026-06-05 12:39:24 -05:00
Greg Ohlsen 7f6d66d246 Add image to README for enhanced documentation
Added an additional image to the README for better visualization.
2026-06-05 12:22:29 -05:00
Greg Ohlsen e0d14eedd8 Replace old image with updated version in README
Updated image in README to a new source.
2026-06-05 12:09:41 -05:00
Greg Ohlsen 68ab83775a Replace image source in README
Updated image in README to a new source.
2026-06-05 12:08:50 -05:00
Greg Ohlsen 81d276f003 Add screenshot of the control panel
Added an image to illustrate the panel functionality.
2026-06-05 12:06:05 -05:00
gregowahoo a0fce1557c docs: README — document multi-digit keyboard shortcuts 2026-05-25 08:16:30 -05:00
gregowahoo 97a5b559ad v0.4.3: support multi-digit keyboard shortcuts (10, 11, ...)
Single-digit hotkeys (1..9) jump immediately. With more than 9 groups,
typing a digit now buffers briefly so two-digit row numbers (10, 11,
12, ...) work too. Buffer commits on:
  - 350ms idle (lets "1" still jump to row 1 after a short pause)
  - any non-digit key (flushes pending buffer first)
  - "dead end" digits where no longer prefix could match any row
    (e.g. with 15 rows, pressing "2" commits instantly since 2x is
     out of range — keeps single-digit feel snappy where possible)
2026-05-25 07:49:10 -05:00
Greg 7790bf5d96 Rename panel title to 'Workflow Group Navigator' 2026-05-25 07:35:49 -05:00
Greg cfdfa38412 Public-facing README + MIT LICENSE for v0.4.1 release v0.4.1 2026-05-25 05:40:44 -05:00
Greg a654d001ec v0.4.1: add Run button to the toolbar
Sits next to Disable All. Calls queuePromptSafely() — which tries
app.queuePrompt(0) first, then falls back to clicking the canvas-side
Run button if the API shape isn't available.

Distinct green styling (#2d7d46) so it doesn't blend with the blue
Enable/Disable buttons. Queues the prompt using whatever groups are
currently enabled, same as ComfyUI's own Run.
2026-05-25 05:16:59 -05:00
Greg 0861d7ed13 v0.4.0: drag-to-reorder rows + custom order persisted to localStorage
Lets users define a workflow sequence (step 1 → step 2 → ...) by
dragging rows in the panel. Order is independent of rgthree's
auto-sort and persists across reloads.

How it works:
- Each row gets draggable="true" + HTML5 DnD handlers.
- On drop, reorderGroup(moving, target, before|after) computes the new
  ordering, writes it to localStorage (ORDER_KEY), and re-renders.
- Drop indicator: inset top/bottom box-shadow in the accent color
  showing where the dragged row will land.
- sortByCustomOrder() applied at the end of getNavigableGroups so the
  matchColors filtering still happens first.
- Groups not yet in the saved order keep relative order and end up
  after listed ones. New groups auto-append on the next reorder write.

Shortcut badges follow the new order — 1..9 always map to the first 9
rows as they appear in the panel, so dragging a group to the top
assigns it shortcut "1" (assuming no custom override).

Settings panel gains two reset buttons: "Reset row order" (just the
order) and "Reset all settings" (colors + shortcuts + order).
2026-05-25 05:06:26 -05:00
Greg 31449d6cd1 v0.3.7: force-state via doModeChange, fixes Enable All skipping groups
whose contents overlap a previously-enabled group

rgthree's public widget.toggle(value) is misleadingly named — it doesn't
honor the passed value, it calls doModeChange() with no force argument,
and doModeChange then COMPUTES the next state from the current node
modes:
   newValue = !hasAnyActiveNodes
So if any inner node is already ALWAYS (e.g. shared with a
previously-toggled overlapping/parent group), toggle(true) actually
DISABLES the group rather than enabling it. Symptom: Enable All flipped
13 of 14 groups; "Place an image" (whose 25 nodes overlapped with an
earlier-enabled neighbor) stayed at NO.

Fix: setMuterWidget now calls widget.doModeChange(target, true) directly
— the (force, skipOtherNodeCheck) path that actually forces the state
and skips rgthree's "max one"/"always one" restriction handling that
could re-disable peers. Falls back to the old toggle/value paths only
for non-rgthree widgets.
2026-05-25 04:58:27 -05:00
Greg c54f339a5d v0.3.6: shortcut badges reflect custom mappings + re-render on change
Two related fixes:
- New getShortcutKeyForGroup(title, index): looks up which key would
  jump to this group. Inverse of resolveShortcut. Custom mappings
  take precedence; defaults (1..9) only show when not overridden.
- renderList now uses that helper for the badge instead of hardcoding
  String(index + 1). So a custom shortcut '0' → "Text to Image (Flux.2
  Klein 9B)" now displays "0" on that row, and the slot that used to
  show "1" loses its badge if "1" is reassigned elsewhere.
- persistShortcutsFromDom calls scheduleRender() so badges update live
  as the user types in the settings panel.

Bumped to 0.3.6.
2026-05-25 04:40:38 -05:00
Greg c368aeb137 v0.3.5: custom shortcuts override only the keys they bind, not all keys
Regression introduced in v0.3.1: resolveShortcut() bailed out as soon
as ANY saved shortcut map existed. So adding one custom binding (say
'g' → "Pulid for Flux 1") silently killed the 1..9 defaults entirely —
every other key returned null.

Fix: only return the custom mapping when there's an explicit entry for
the pressed key. Otherwise fall through to the 1..9 default. Net effect:
- Empty map → all defaults active (already correct from v0.3.4)
- Map with {g: ...} → 'g' jumps to the custom target; 1-9 still default
- Map with {1: "Skin repair"} → '1' overrides default, 2-9 still default
2026-05-25 04:31:38 -05:00
Greg 61a4a20fb5 v0.3.4: fix shortcut regressions — empty map + rgthree Bookmark collision
Two related bugs in keyboard shortcuts after the user cleared their
custom mappings:

1) Empty saved map killed defaults. resolveShortcut() short-circuited
   on any saved map (including {}) instead of falling through to the
   1..9 defaults. Now: loadShortcuts() returns null for empty/missing/
   malformed values, and saveShortcuts() actively removes the
   localStorage entry when the map empties — so deleting every row in
   the settings panel restores the defaults.

2) rgthree's Bookmark node listens on its own KEY_EVENT_SERVICE — if
   the user's bookmark and a navigator default share a key (e.g. both
   "1"), pressing it caused MY handler to jump first, then rgthree's
   bookmark would jump again, overriding the visual.

   Fix: onKeyDown now calls preventDefault + stopImmediatePropagation
   + stopPropagation when we claim a key. The window-capture handler
   runs before bubble-phase or service-dispatch listeners, so rgthree
   no longer gets a chance to override.

   If the user WANTS the rgthree bookmark to win for a specific key,
   they can remove that key from the navigator settings — resolveShortcut
   returns null and we don't intercept.
2026-05-25 04:25:38 -05:00
Greg 40394461ad v0.3.3: visual highlight for enabled rows + fix shortcut persistence
Two issues:

1) Enabled rows now have a visual indicator beyond the toggle pill:
   - Bold title text tinted toward the accent color
   - Left edge stripe in the accent color (via inset box-shadow)
   - Subtle gradient wash from the accent color into the row bg
   All driven by --cnv-accent so it picks up the user's custom color.
   Applied at render time AND on every periodic sync so external
   toggles (via the canvas muter directly) update the panel too.

2) Shortcut edits weren't persisting. Root cause: I only attached a
   "change" event listener to the shortcut list, and text inputs only
   fire change on blur. So typing a key char then immediately picking a
   group from the select could lose the unsaved key value when the
   panel closed without the input ever blurring.
   Fix: also attach an "input" listener — fires per keystroke for text
   inputs and on selection for selects. Either path now persists.
2026-05-24 23:30:13 -05:00
Greg fa4e1af5c5 v0.3.2: click anywhere on header to collapse + bigger collapse button
Header now distinguishes click vs drag:
- mousedown → mouseup with <4px total movement = treated as a CLICK,
  toggles collapse (same as clicking the arrow)
- More movement = actual drag, keeps existing behavior
- Threshold avoids accidental toggling during tiny mouse jitters

Also enlarged the collapse arrow button: bigger font, padded box with
border + background tint, easier to hit. Either gesture (header click
or arrow click) does the same thing now.
2026-05-24 16:55:09 -05:00
Greg cf256c64f5 v0.3.1: settings panel with configurable colors + keyboard shortcuts
Click the ⚙ button in the toolbar → settings slide down between toolbar
and group list.

Colors (4 pickers, live preview, persist to localStorage):
- Body background      (defaults to rgthree teal #0e283f)
- Header bar           (#1e4f88)
- Accent / toggle ON   (#2864ad)
- Row hover            (#163657)

All panel styling now reads from CSS custom properties so the live
update is just panel.style.setProperty(key, value).

Keyboard shortcuts (overrides defaults):
- Add rows one at a time: type a key char, pick a group from a dropdown
- × button removes a row
- Empty list = defaults restore (1..9 → first 9 groups by order)
- Single-key only (a-z, 0-9). Modifier keys ignored (Ctrl/Meta/Alt).

Reset button wipes both localStorage entries and rebuilds settings UI
with defaults.

Refactored onKeyDown to go through resolveShortcut() so the same code
path handles defaults and user overrides.
2026-05-24 16:43:47 -05:00
Greg 31e4591993 v0.3.0: strip down + toolbar with Enable All / Disable All
Per user feedback:
- REMOVE filter / search input — wasn't useful for 15 groups
- REMOVE color dot — was confusing, looked like a status indicator
  but did nothing
- REMOVE "Enable " prefix on row titles — just show the group name
- REMOVE → jump button per row — clicking the row body already jumps
- REMOVE ⋯ menu per row — its actions weren't pulling weight
- REMOVE right-click context menu — same reason
- ADD toolbar between header and list:
    [Enable All] [Disable All]              [⚙ Settings]
  Enable All / Disable All flip every widget in every muter at once.
  Settings opens an alert stub for now — actual color + shortcut
  customization will land in v0.3.1.

Net result: each row is just `Group Name  [N]  [toggle pill]`.
Click toggle pill = flip. Click anywhere else on the row = jump.

Bumped to 0.3.0 (breaking UI change).
2026-05-24 16:17:29 -05:00
Greg 76116feebc v0.2.3: re-render when muter widgets appear or placeholders linger
Bug: rows could be permanently stuck as placeholders (no click handler,
no toggle pill, no nothing) if the panel rendered BEFORE the rgthree
Fast Groups Muter's widgets were constructed. The poll interval only
re-rendered when graph._groups.length changed; once placeholders shipped,
they stayed even after widgets caught up.

Now the interval also tracks total muter-widget count and re-renders
when it changes. It also force-re-renders any time placeholders exist
AND any muter widget exists — covers the case where my matcher and the
muter both happen to start with the same widget count but different
matches.

Confirmed in autonomous browser test that widget-count-change triggers
the re-render path. Could not fully verify the rendered output flips
to real toggles because loadGraphData() in headless test environment
does NOT construct rgthree's muter widgets the same way a real workflow
load does (widgets stay empty in the test). In a real user session,
widgets ARE present, so the fix applies.
2026-05-24 16:06:03 -05:00
Greg 20e587fb60 v0.2.2: actually toggle via widget.toggle() — fix 'comes back and says no'
Bug: clicking the toggle pill in the panel would visually flip to 'yes'
for ~600ms then revert to 'no'. My code was setting widget.value.toggled
directly, but rgthree's FastGroupsToggleRowWidget has a separate
doModeChange() method that performs the real work — calling
changeModeOfNodes() on every node inside the group. Without that call,
rgthree's own update loop re-reads node modes (still ALWAYS — i.e.
'enabled'), recomputes group.rgthree_hasAnyActiveNode, and overwrites
widget.toggled back to its previous value.

Fix: call widget.toggle(value), the public method on the
FastGroupsToggleRowWidget class. That sets value AND triggers
doModeChange(). Kept the old value-set path as fallback for unknown
widget shapes.
2026-05-24 13:16:15 -05:00
Greg c91f05a9a2 v0.2.1: restyle for closer rgthree muter visual match — flat dark teal body, blue header bar, tighter rows 2026-05-24 13:12:55 -05:00
Greg ffc13469ea v0.2.0: restyle to mirror rgthree Fast Groups Muter aesthetic
After realizing the canvas-Floater approach can't escape canvas zoom
(pinned canvas nodes always scale with ds.scale, making them unusable
when panned out), pivoted back to this DOM-overlay panel. It naturally
renders at constant size regardless of zoom.

This commit just makes the panel LOOK like rgthree's muter so it doesn't
feel like a separate UI:

- Dark blue card with rgthree-style blue header bar
- Each row: color dot + "Enable {Group Name}" + toggle pill + → + ⋯
- Toggle pill is a real pill: rounded rectangle with "no"/"yes" label
  and a glowing knob that pulses when on (matches rgthree muter visuals)
- Alternating row backgrounds for readability
- Toggle pill is bidirectionally wired to the corresponding rgthree
  widget — flip in panel → muter widget toggles → panel pill animates
  (and vice versa via the existing 600ms sync)

Bumped CNV_VERSION to 0.2.0.
2026-05-24 13:06:59 -05:00
Greg c60c42e7c0 v0.1.0: redesign as floating muter mirror
Each row in the panel is now a real two-way toggle wired to the
corresponding widget on rgthree's Fast Groups Muter. Click the checkbox
in the panel → muter widget flips. Toggle the muter widget directly on
the canvas → panel checkbox catches up on the next poll (600ms). Save +
load works because all state still lives in rgthree.

Per row layout:
  [checkbox] [color dot] Group Name [shortcut N] [→ jump] [⋯ menu]

Click body of row = jump. Explicit → button = same jump (visible
affordance). ⋯ opens the same context menu as right-click. Groups with
no managing muter render a dashed placeholder where the checkbox would
be (visual signal: "this group is unmanaged").

Kept: drag-to-reposition, collapse, search, matchColors filter (only
groups in the primary muter render), keyboard 1-9 = jump, version
marker, all rgthree integration (single-muter Solo, all-muter Reset).

Bumped to v0.1.0 since the row contract changed.
2026-05-24 12:14:41 -05:00
Greg 1a84f8dd52 Add version marker (window.__cnv_version + console log) for cache debugging 2026-05-24 11:58:24 -05:00
Greg bd295401d0 Rename Solo menu items to plain English
User found 'Solo' confusing (audio-mixer metaphor doesn't land).

- 'Solo & Run this group'  →  'Run only this group'
- 'Solo only (mute others, don't run)'  →  'Mute other groups'

Internal function names (soloGroup, soloGroupViaMuter) kept for code
brevity — they're never user-visible.
2026-05-24 11:56:07 -05:00
Greg aa08f159b1 Rename Solo menu + add Solo & Run that actually queues a prompt
Previous label 'Solo run this group' was misleading — it only configured
the muter (turn off other groups in the same muter scope) but didn't
actually queue the prompt. User had to click Run separately, which made
the action feel broken.

Now two menu items:
- 'Solo & Run' — configures the muter, waits a tick for rgthree callbacks
  to settle, then calls app.queuePrompt(0) (falls back to clicking the Run
  button if the API shape changes). One click → solo + queue.
- 'Solo only (mute others, don't run)' — same as the old behavior for
  cases where you want to inspect / tweak before running.

Reset stays as-is.
2026-05-24 11:52:23 -05:00
Greg f543c19d9c Filter panel by primary muter's matchColors scope
User color-codes sub-groups (e.g. gold 'Flux Klein Workflow' inside a
larger blue 'Place an image' workflow) and routes them through a separate
Fast Groups Muter via matchColors. The intent: sub-groups aren't
top-level navigation targets, they're implementation details of their
parent workflow.

New getNavigableGroups(): if any muter exists, pick the one with the most
managed widgets as the 'primary navigation muter' and show only its
groups in the panel. Sub-groups managed by smaller scoped muters get
filtered out. No muter present → show every group (previous behavior).

Header count now shows 'N / M' when filtering, with tooltip explaining
how many sub-groups were hidden. Keyboard 1-9 follows the same filtered
list.
2026-05-24 11:45:29 -05:00
Greg 7f83df4bfd Respect rgthree matchColors: scope Solo to owning muter only
Workflows often use multiple Fast Groups Muters with the matchColors
property, splitting groups into independent toggle scopes (e.g. one muter
for all blue-color groups, another for the gold "Flux Klein Workflow").

Previous Solo turned off widgets across ALL muters indiscriminately —
soloing a blue group would clobber the gold muter's state too, defeating
the user's intentional color-based partitioning.

Now: getMutersAndWidgets() returns per-muter widget maps. Solo finds the
single muter that owns the target group (via findMuterOwningGroup) and
toggles only within that muter's widget set. Other muters are left
exactly as the user set them. Reset still spans all muters (turn
everything on).
2026-05-24 11:38:52 -05:00
Greg b3dd04db28 Fix rgthree FastGroupsMuter integration
Real widget structure (from live diagnostic):
- name:  "RGTHREE_TOGGLE_AND_NAV" (generic, not per-group)
- type:  "custom" (not "toggle" or "RGTHREE_TOGGLE")
- label: "Enable {group_title}" — the only link from widget to group
- value: { toggled: bool } — an object, not a plain boolean

My previous filter rejected every widget (wrong type guard) and tried to
set .value = true (wrong shape). Result: solo/reset did nothing.

Fix:
- Parse "Enable {title}" out of widget.label to map widget → group title
- Mutate widget.value.toggled, preserving other keys, and call w.callback(value)
- Scan ALL muters on the graph (workflow may have 2+); aggregate their
  widget maps and only dirty the muters we actually touched
- Fall back to per-node mode flag when a muter is present but its widget
  set doesn't include the target group, in addition to the no-muter case
2026-05-24 11:32:54 -05:00
Greg c3b1215206 Initial scaffold for comfyui-navigator
JS-only ComfyUI extension. Floating right-anchored panel that:
- Auto-lists every LGraph group in the current workflow
- Click row → animated pan+zoom to fit the group on screen
- Search box at the top filters by title
- Right-click row → Solo run / Center / Reset
- Solo uses rgthree's Fast Groups Muter (driven via widget callbacks)
  when present; otherwise falls back to node.mode = NEVER on every node
  outside the soloed group's bounds
- Keyboard 1-9 jumps to the first nine groups
- Panel is draggable; position + collapsed state persisted to localStorage
- Hidden when graph has < 2 groups

No Python node, no drawNode hooks, no view-filtering — only canvas pan/zoom
+ rgthree widget poking + node.mode flag setting. None of the fragile
internal hooks that comfyui-tabs depended on.
2026-05-24 11:23:18 -05:00