Add Text Gate (Manual Pass) design + implementation plan

Blocking text gate: pauses, shows incoming text in an editable box, Pass
emits the edited text. Optional any-type signal input + signal passthrough
output for ordering. Reuses gate_bus via an additive string payload channel
with a should_cancel hook so the Pass-only gate still honors global Cancel
(processing_interrupted). TDD plan; comfy imports stay lazy for testability.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-21 18:41:17 +02:00
parent ec8e1b9598
commit 32f616e067
2 changed files with 377 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
# Text Gate (Manual Pass) — Design
Date: 2026-06-21
Status: Approved (brainstorming complete, ready for implementation plan)
## 1. Purpose
A simple blocking gate for text: during a run it **pauses**, shows the incoming text in an
**editable** box, and waits for a **Pass** click; on pass it emits the (possibly edited)
text. An optional any-type **signal** input lets you force execution order, and a
**signal** passthrough output lets you chain gates in a fixed sequence. Fourth node in the
`ComfyUI-Datasete-Gates` suite; reuses the Image Gate's `gate_bus` blocking infra.
## 2. IO
| dir | name | type | notes |
|---|---|---|---|
| in | `text` | STRING (`forceInput`) | incoming text from upstream |
| in (optional) | `signal` | `*` (AnyType) | accepts anything; only used to sequence this node after its source |
| hidden | `unique_id` | UNIQUE_ID | keys the pause |
| out | `text` | STRING | the edited text passed by the user |
| out | `signal` | `*` (AnyType) | passthrough of the input signal (fires on pass) → chain ordering |
## 3. Behavior (the pause)
On execute:
1. `GateBus.arm(unique_id)`; push the incoming text to the UI
(`PromptServer.send_sync("datasete-textgate-show", {id, text})`).
2. Frontend shows an **editable textarea** prefilled with the text + a **Pass** button.
3. **Block** on `GateBus.wait_payload(unique_id, should_cancel=...)` until Pass.
4. **Pass** → frontend POSTs the edited text to `/datasete_text_gate/pass`; the node returns
`(edited_text, signal)`.
`IS_CHANGED` returns `nan` → pauses on every run.
**No Stop button**, but the wait loop honors ComfyUI's global Cancel via a `should_cancel`
callback (`comfy.model_management.processing_interrupted`) so a queue-cancel can't deadlock
the gate; on cancel it raises `InterruptProcessingException`.
## 4. Reuse / changes to existing files (all additive)
- `gates/gate_bus.py` — add a **payload channel**: `payloads` dict, `put_payload`,
`wait_payload(..., should_cancel=None)`; `arm()` also clears `payloads`. Existing
int-choice/mask API untouched (Image Gate keeps working).
- `gates/gate_server.py` — add `send_text()` + route `POST /datasete_text_gate/pass`.
- `gates/textgate.py` *(new)*`AnyType("*")` + `ANY`; the `TextGate` node (lazy comfy
imports so it unit-tests without ComfyUI).
- `web/text_gate.js` *(new)* — listen for `datasete-textgate-show`, render editable textarea
+ Pass, POST the edited text.
- root `__init__.py` — merge `TextGate` into the mappings (gate_server already imported).
## 5. Edge cases
- Signal not connected → `signal=None`; output `None` (downstream still ordered by the
dependency).
- `AnyType` output value `None` connects fine (the `__ne__`→False trick makes type checks
pass), matching the installed custom-node convention.
- Empty incoming text → empty textarea; Pass emits whatever's there (possibly `""`).
- Global queue-cancel while blocked → clean interrupt (see §3).
## 6. Testing
- pytest: `gate_bus` payload roundtrip + `arm` clears payloads + `wait_payload` cancel via
flag and via `should_cancel`; `AnyType` equals-everything; `TextGate` RETURN_TYPES/NAMES
and `IS_CHANGED==nan`.
- Manual (live): pause shows editable text, edit + Pass emits edited text; signal in forces
order; signal out chains to a second gate; global Cancel unblocks cleanly.