Files
Claude 7b02fcfd58 feat(engine): per-variable negatives for send-to-negative
Wildcard options, fixed values and combines can carry a `negative`, and a
derivation action gains the `negative` ("Add to negative") mode. The
engine files negatives per variable in `__wp_negatives__`, carries them
across nodes, and the Assembler gets a `negative` output built from the
variables its template rendered, filled into a `negative_template` at
`$negatives`. Negatives resolve on their own seed stream and refs inside
them resolve quietly, so picks never shift. Injector rows can set a
negative, the Prompt Cleaner cleans an optional negative with its own
rule column, and the preview endpoint returns the negatives table.
Schema v8 stamps payloads that use a negative.
2026-09-30 00:50:47 +00:00

76 lines
2.2 KiB
Python

"""End-to-end node integration — simulate a small ComfyUI workflow."""
import json
from wp_nodes.assembler_node import WPPromptAssembler
from wp_nodes.context_node import WPContext
from wp_nodes.debug_node import WPDebug
from wp_nodes.types import ContextPayload
def _mods(entries):
return json.dumps({
"version": 1,
"modules": [{
"id": "auto",
"type": "fixed_values",
"entries": entries,
}],
})
def test_chain_of_two_context_nodes_into_assembler():
# Upstream context sets style + subject.
upstream = WPContext.execute(
seed=1,
wp_modules=_mods([
{"variable_name": "style", "value": "photoreal"},
{"variable_name": "subject", "value": "knight"},
]),
upstream=None,
).values[0]
# Downstream context adds environment vars + overrides style.
downstream = WPContext.execute(
seed=2,
wp_modules=_mods([
{"variable_name": "style", "value": "painted"},
{"variable_name": "location", "value": "forest"},
]),
upstream=upstream,
).values[0]
assert isinstance(downstream, ContextPayload)
assert downstream.context == {
"subject": "knight",
"style": "painted",
"location": "forest",
}
# Assembler renders the final template.
assembled = WPPromptAssembler.execute(
context=downstream,
template="A $style $subject in the $location",
)
assert assembled.values == ("A painted knight in the forest", "")
def test_debug_node_snapshots_downstream_context():
upstream = WPContext.execute(
seed=7,
wp_modules=_mods([
{"variable_name": "style", "value": "photoreal"},
]),
upstream=None,
).values[0]
out = WPDebug.execute(context=upstream, wp_viewer=None)
snapshot = json.loads(out.ui["wp_debug_snapshot"][0])
# Flat snapshot shape — user vars at the top, engine internals
# under `__wp_*` keys ready for the per-tab readers in
# `DebugViewer.vue`.
assert snapshot["style"] == "photoreal"
assert snapshot["__wp_node_seed__"] == 7
assert len(snapshot["__wp_trace__"]) == 1