From b4639a73d34610b6587626a26f52766d237d7035 Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Wed, 1 Jul 2026 13:44:50 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20text=20gate=20protected=20mode=20?= =?UTF-8?q?=E2=80=94=20standalone=20text=20node=20(backend)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit protected=True makes run() emit stored_text and ignore upstream with no pause; IS_CHANGED caches on stored_text when protected (NaN otherwise). text input is now optional so the node can run standalone. Co-Authored-By: Claude Opus 4.8 --- gates/textgate.py | 25 ++++++++++++++++++------- tests/test_textgate.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/gates/textgate.py b/gates/textgate.py index 3abcc7a..13a16f6 100644 --- a/gates/textgate.py +++ b/gates/textgate.py @@ -22,26 +22,37 @@ class TextGate: @classmethod def INPUT_TYPES(cls): + # `text` is optional so the node can run standalone in protected mode. + # `protected` + `stored_text` are serializing widgets carrying the + # authored text-node state (stored_text is hidden by the frontend). return { - "required": { - "text": ("STRING", {"forceInput": True}), - }, "optional": { + "text": ("STRING", {"forceInput": True}), "signal": (ANY, {}), + "protected": ("BOOLEAN", {"default": False}), + "stored_text": ("STRING", {"default": "", "multiline": True}), }, "hidden": {"unique_id": "UNIQUE_ID"}, } @classmethod - def IS_CHANGED(cls, **kwargs): - return float("nan") + def IS_CHANGED(cls, protected=False, stored_text="", **kwargs): + # Protected = plain text node: cache on the authored text so downstream + # only re-runs when it changes. Otherwise never cache (always pause). + return stored_text if protected else float("nan") + + def run(self, unique_id=None, text=None, signal=None, + protected=False, stored_text=""): + if protected: + # Standalone text node: emit the authored text, ignore upstream, no + # pause. Returns before importing comfy, so it stays import-safe. + return (stored_text, signal) - def run(self, text, unique_id, signal=None): from . import gate_server import comfy.model_management as mm gate_bus.GateBus.arm(unique_id) - gate_server.send_text(unique_id, text) + gate_server.send_text(unique_id, text or "") try: edited = gate_bus.GateBus.wait_payload( unique_id, should_cancel=mm.processing_interrupted) diff --git a/tests/test_textgate.py b/tests/test_textgate.py index 8feb4bf..1979bb6 100644 --- a/tests/test_textgate.py +++ b/tests/test_textgate.py @@ -16,3 +16,31 @@ def test_textgate_io_shape(): def test_textgate_is_changed_nan(): v = textgate.TextGate.IS_CHANGED(text="hi", unique_id="1") assert math.isnan(v) + + +def test_textgate_text_input_is_optional(): + it = textgate.TextGate.INPUT_TYPES() + assert "text" in it["optional"] + assert "protected" in it["optional"] + assert "stored_text" in it["optional"] + + +def test_textgate_protected_returns_stored_text_without_pause(): + # protected mode must return the stored text directly — no GateBus, no comfy + out = textgate.TextGate().run( + unique_id="1", text="from upstream", signal="sig", + protected=True, stored_text="my authored text", + ) + assert out == ("my authored text", "sig") + + +def test_textgate_is_changed_protected_returns_stored_text(): + v = textgate.TextGate.IS_CHANGED( + unique_id="1", protected=True, stored_text="frozen") + assert v == "frozen" + + +def test_textgate_is_changed_not_protected_is_nan(): + v = textgate.TextGate.IS_CHANGED( + unique_id="1", protected=False, stored_text="ignored") + assert math.isnan(v)