5 Commits

Author SHA1 Message Date
Ethanfel 4fe9a9c958 feat: add BinaryIndexDecoder node (INT index → 3 BOOLEANs, binary encoding)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 00:05:15 +02:00
Ethanfel 820cb426aa test: add missing index 5 and 6 cases for BinaryIndexDecoder
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 00:04:15 +02:00
Ethanfel b75b177591 test: add failing tests for BinaryIndexDecoder node
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 00:02:43 +02:00
Ethanfel c8cc397cc6 docs: add BinaryIndexDecoder implementation plan
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 00:01:36 +02:00
Ethanfel f0e785afab docs: add BinaryIndexDecoder node design
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 00:00:56 +02:00
4 changed files with 321 additions and 2 deletions
@@ -0,0 +1,67 @@
# BinaryIndexDecoder Node — Design
## Summary
A standalone ComfyUI utility node that converts an integer index into 3 boolean
outputs using binary (bit-field) encoding. Intended for use with loop counters to
gate multiple processing branches simultaneously.
## Node Spec
| Field | Value |
|---|---|
| Class name | `BinaryIndexDecoder` |
| Display name | `Binary Index Decoder` |
| Category | `JSON Manager/utils` |
| Function | `decode` |
### Inputs
| Name | Type | Default | Range |
|---|---|---|---|
| `index` | INT | 0 | 07 |
### Outputs
| Name | Type |
|---|---|
| `flag_0` | BOOLEAN |
| `flag_1` | BOOLEAN |
| `flag_2` | BOOLEAN |
### Logic
```
flag_0 = bool((index >> 0) & 1)
flag_1 = bool((index >> 1) & 1)
flag_2 = bool((index >> 2) & 1)
```
### Truth table
| index | flag_0 | flag_1 | flag_2 |
|---|---|---|---|
| 0 | F | F | F |
| 1 | T | F | F |
| 2 | F | T | F |
| 3 | T | T | F |
| 4 | F | F | T |
| 5 | T | F | T |
| 6 | F | T | T |
| 7 | T | T | T |
## Implementation Notes
- Lives in `project_loader.py` alongside other project nodes
- Added to `PROJECT_NODE_CLASS_MAPPINGS` and `PROJECT_NODE_DISPLAY_NAME_MAPPINGS`
- No JavaScript extension needed (no source sync, no dynamic widgets)
- No NiceGUI UI changes needed
- `IS_CHANGED` not needed (output is deterministic from input)
## Testing
9 tests in `tests/test_project_loader.py::TestBinaryIndexDecoder`:
- Input types include `index` as INT
- All 8 index values (07) produce correct boolean tuple
- Out-of-range index (e.g. 8) clamps to 07 or wraps gracefully
- `NodeMappings` test updated: 5 nodes, mappings length == 5
@@ -0,0 +1,166 @@
# BinaryIndexDecoder Node — Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Add a standalone ComfyUI node `BinaryIndexDecoder` that converts an integer index to 3 boolean outputs using binary (bit-field) encoding.
**Architecture:** Single class in `project_loader.py`, no JS extension needed, no NiceGUI changes. Takes `index` INT, returns `(flag_0, flag_1, flag_2)` as BOOLEAN using bit-shift logic. Added to existing node mappings.
**Tech Stack:** Python, ComfyUI node API, pytest
---
### Task 1: Write failing tests for BinaryIndexDecoder
**Files:**
- Modify: `tests/test_project_loader.py` (append new test class at end, before `TestNodeMappings`)
- Modify: `tests/test_project_loader.py` — update `TestNodeMappings.test_mappings_exist` to expect 5 nodes
**Step 1: Add the test class**
Append this class before `TestNodeMappings` in `tests/test_project_loader.py`:
```python
class TestBinaryIndexDecoder:
def test_input_types(self):
from project_loader import BinaryIndexDecoder
inputs = BinaryIndexDecoder.INPUT_TYPES()
assert "index" in inputs["required"]
assert inputs["required"]["index"][0] == "INT"
def test_three_boolean_outputs(self):
from project_loader import BinaryIndexDecoder
assert BinaryIndexDecoder.RETURN_TYPES == ("BOOLEAN", "BOOLEAN", "BOOLEAN")
assert BinaryIndexDecoder.RETURN_NAMES == ("flag_0", "flag_1", "flag_2")
def test_category(self):
from project_loader import BinaryIndexDecoder
assert BinaryIndexDecoder.CATEGORY == "JSON Manager/utils"
def test_index_0(self):
from project_loader import BinaryIndexDecoder
assert BinaryIndexDecoder().decode(0) == (False, False, False)
def test_index_1(self):
from project_loader import BinaryIndexDecoder
assert BinaryIndexDecoder().decode(1) == (True, False, False)
def test_index_2(self):
from project_loader import BinaryIndexDecoder
assert BinaryIndexDecoder().decode(2) == (False, True, False)
def test_index_3(self):
from project_loader import BinaryIndexDecoder
assert BinaryIndexDecoder().decode(3) == (True, True, False)
def test_index_4(self):
from project_loader import BinaryIndexDecoder
assert BinaryIndexDecoder().decode(4) == (False, False, True)
def test_index_7(self):
from project_loader import BinaryIndexDecoder
assert BinaryIndexDecoder().decode(7) == (True, True, True)
```
Also update `TestNodeMappings.test_mappings_exist`:
```python
def test_mappings_exist(self):
from project_loader import PROJECT_NODE_CLASS_MAPPINGS, PROJECT_NODE_DISPLAY_NAME_MAPPINGS
assert "ProjectLoaderDynamic" in PROJECT_NODE_CLASS_MAPPINGS
assert "ProjectSource" in PROJECT_NODE_CLASS_MAPPINGS
assert "ProjectKey" in PROJECT_NODE_CLASS_MAPPINGS
assert "ProjectResolution" in PROJECT_NODE_CLASS_MAPPINGS
assert "BinaryIndexDecoder" in PROJECT_NODE_CLASS_MAPPINGS
assert len(PROJECT_NODE_CLASS_MAPPINGS) == 5
assert len(PROJECT_NODE_DISPLAY_NAME_MAPPINGS) == 5
```
**Step 2: Run tests to verify they fail**
```bash
python -m pytest tests/test_project_loader.py::TestBinaryIndexDecoder -v
```
Expected: FAIL with `ImportError: cannot import name 'BinaryIndexDecoder'`
**Step 3: Commit the failing tests**
```bash
git add tests/test_project_loader.py
git commit -m "test: add failing tests for BinaryIndexDecoder node"
```
---
### Task 2: Implement BinaryIndexDecoder
**Files:**
- Modify: `project_loader.py` — add class after `ProjectResolution`, update mappings
**Step 1: Add the class**
Insert after the `ProjectResolution` class (before `# --- Mappings ---`) in `project_loader.py`:
```python
class BinaryIndexDecoder:
"""Decodes an integer index into 3 boolean flags using binary (bit-field) encoding.
index 0 → (False, False, False)
index 1 → (True, False, False) # bit 0
index 2 → (False, True, False) # bit 1
index 3 → (True, True, False) # bits 0+1
index 4 → (False, False, True) # bit 2
...
index 7 → (True, True, True)
"""
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"index": ("INT", {"default": 0, "min": 0, "max": 7}),
}
}
RETURN_TYPES = ("BOOLEAN", "BOOLEAN", "BOOLEAN")
RETURN_NAMES = ("flag_0", "flag_1", "flag_2")
FUNCTION = "decode"
CATEGORY = "JSON Manager/utils"
OUTPUT_NODE = False
def decode(self, index: int):
return (
bool((index >> 0) & 1),
bool((index >> 1) & 1),
bool((index >> 2) & 1),
)
```
**Step 2: Update mappings**
In `PROJECT_NODE_CLASS_MAPPINGS`, add:
```python
"BinaryIndexDecoder": BinaryIndexDecoder,
```
In `PROJECT_NODE_DISPLAY_NAME_MAPPINGS`, add:
```python
"BinaryIndexDecoder": "Binary Index Decoder",
```
**Step 3: Run all tests**
```bash
python -m pytest tests/test_project_loader.py -v
```
Expected: all tests PASS (42 existing + 10 new = 52 total)
**Step 4: Commit**
```bash
git add project_loader.py tests/test_project_loader.py
git commit -m "feat: add BinaryIndexDecoder node (INT index → 3 BOOLEANs, binary encoding)"
git push
```
+36
View File
@@ -349,12 +349,47 @@ class ProjectResolution:
return (to_int(entry[0]), to_int(entry[1]), seed) return (to_int(entry[0]), to_int(entry[1]), seed)
class BinaryIndexDecoder:
"""Decodes an integer index into 3 boolean flags using binary (bit-field) encoding.
index 0 → (False, False, False)
index 1 → (True, False, False) # bit 0
index 2 → (False, True, False) # bit 1
index 3 → (True, True, False) # bits 0+1
index 4 → (False, False, True) # bit 2
...
index 7 → (True, True, True)
"""
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"index": ("INT", {"default": 0, "min": 0, "max": 7}),
}
}
RETURN_TYPES = ("BOOLEAN", "BOOLEAN", "BOOLEAN")
RETURN_NAMES = ("flag_0", "flag_1", "flag_2")
FUNCTION = "decode"
CATEGORY = "JSON Manager/utils"
OUTPUT_NODE = False
def decode(self, index: int):
return (
bool((index >> 0) & 1),
bool((index >> 1) & 1),
bool((index >> 2) & 1),
)
# --- Mappings --- # --- Mappings ---
PROJECT_NODE_CLASS_MAPPINGS = { PROJECT_NODE_CLASS_MAPPINGS = {
"ProjectLoaderDynamic": ProjectLoaderDynamic, "ProjectLoaderDynamic": ProjectLoaderDynamic,
"ProjectSource": ProjectSource, "ProjectSource": ProjectSource,
"ProjectKey": ProjectKey, "ProjectKey": ProjectKey,
"ProjectResolution": ProjectResolution, "ProjectResolution": ProjectResolution,
"BinaryIndexDecoder": BinaryIndexDecoder,
} }
PROJECT_NODE_DISPLAY_NAME_MAPPINGS = { PROJECT_NODE_DISPLAY_NAME_MAPPINGS = {
@@ -362,4 +397,5 @@ PROJECT_NODE_DISPLAY_NAME_MAPPINGS = {
"ProjectSource": "Project Source", "ProjectSource": "Project Source",
"ProjectKey": "Project Key", "ProjectKey": "Project Key",
"ProjectResolution": "Project Resolution", "ProjectResolution": "Project Resolution",
"BinaryIndexDecoder": "Binary Index Decoder",
} }
+52 -2
View File
@@ -447,6 +447,55 @@ class TestProjectResolution:
assert ProjectResolution.CATEGORY == "JSON Manager/project" assert ProjectResolution.CATEGORY == "JSON Manager/project"
class TestBinaryIndexDecoder:
def test_input_types(self):
from project_loader import BinaryIndexDecoder
inputs = BinaryIndexDecoder.INPUT_TYPES()
assert "index" in inputs["required"]
assert inputs["required"]["index"][0] == "INT"
def test_three_boolean_outputs(self):
from project_loader import BinaryIndexDecoder
assert BinaryIndexDecoder.RETURN_TYPES == ("BOOLEAN", "BOOLEAN", "BOOLEAN")
assert BinaryIndexDecoder.RETURN_NAMES == ("flag_0", "flag_1", "flag_2")
def test_category(self):
from project_loader import BinaryIndexDecoder
assert BinaryIndexDecoder.CATEGORY == "JSON Manager/utils"
def test_index_0(self):
from project_loader import BinaryIndexDecoder
assert BinaryIndexDecoder().decode(0) == (False, False, False)
def test_index_1(self):
from project_loader import BinaryIndexDecoder
assert BinaryIndexDecoder().decode(1) == (True, False, False)
def test_index_2(self):
from project_loader import BinaryIndexDecoder
assert BinaryIndexDecoder().decode(2) == (False, True, False)
def test_index_3(self):
from project_loader import BinaryIndexDecoder
assert BinaryIndexDecoder().decode(3) == (True, True, False)
def test_index_4(self):
from project_loader import BinaryIndexDecoder
assert BinaryIndexDecoder().decode(4) == (False, False, True)
def test_index_5(self):
from project_loader import BinaryIndexDecoder
assert BinaryIndexDecoder().decode(5) == (True, False, True)
def test_index_6(self):
from project_loader import BinaryIndexDecoder
assert BinaryIndexDecoder().decode(6) == (False, True, True)
def test_index_7(self):
from project_loader import BinaryIndexDecoder
assert BinaryIndexDecoder().decode(7) == (True, True, True)
class TestNodeMappings: class TestNodeMappings:
def test_mappings_exist(self): def test_mappings_exist(self):
from project_loader import PROJECT_NODE_CLASS_MAPPINGS, PROJECT_NODE_DISPLAY_NAME_MAPPINGS from project_loader import PROJECT_NODE_CLASS_MAPPINGS, PROJECT_NODE_DISPLAY_NAME_MAPPINGS
@@ -454,5 +503,6 @@ class TestNodeMappings:
assert "ProjectSource" in PROJECT_NODE_CLASS_MAPPINGS assert "ProjectSource" in PROJECT_NODE_CLASS_MAPPINGS
assert "ProjectKey" in PROJECT_NODE_CLASS_MAPPINGS assert "ProjectKey" in PROJECT_NODE_CLASS_MAPPINGS
assert "ProjectResolution" in PROJECT_NODE_CLASS_MAPPINGS assert "ProjectResolution" in PROJECT_NODE_CLASS_MAPPINGS
assert len(PROJECT_NODE_CLASS_MAPPINGS) == 4 assert "BinaryIndexDecoder" in PROJECT_NODE_CLASS_MAPPINGS
assert len(PROJECT_NODE_DISPLAY_NAME_MAPPINGS) == 4 assert len(PROJECT_NODE_CLASS_MAPPINGS) == 5
assert len(PROJECT_NODE_DISPLAY_NAME_MAPPINGS) == 5