feat(gui): make the Behavior Input node placeable and configurable - #132
Merged
Conversation
The node was registered with the flow engine and had no presence in the app: no palette entry, no port configuration, no properties. So the closed loop worked when a graph was built in Python and could not be built at all in the Builder, which is where experiments are actually authored. The port gap was the sharp one. setup_node_ports falls back to one generic input and one generic output for an unlisted type, but this node has no inputs and four outputs, so the fallback rendered the wrong shape and left On Enter -- output index 2 -- unreachable. A connection drawn on the canvas would have been wired to Active instead. Maimu gets an entry in the same table, which only relabels its ports from in/out to exec/exec. The behavior is chosen from a dropdown of the loaded model's own vocabulary rather than typed. A mistyped label is silently inert: the node compares the emitted string against the configured one and simply never matches, so the stimulus never fires and nothing anywhere says why. The combo stays editable so a graph can still be authored before any model is loaded, and a label saved from a model that is not currently loaded is preserved rather than dropped. The vocabulary reaches the editor over LiveSignalBus, which is already the boundary between vision and the flow. The camera panel publishes the model's classes when inference goes live, the same place it already hands them to the preview overlay. The alternative -- the properties panel reaching into the camera panel for a worker thread's attribute -- would couple the flow editor to the vision UI's internals. Confirmation window is exposed as a spin box floored at 1, with the latency its docstring documents stated in the panel: at 30 fps, 5 frames is about 167 ms on top of inference.
This was referenced Aug 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #131. That PR landed the closed loop and proved it with an integration test — but the Behavior Input node had no presence in the app at all: no palette entry, no port configuration, no properties. The loop worked when a graph was built in Python and could not be built in the Builder, which is where experiments are actually authored.
The sharp one: ports
setup_node_portsfalls back to([">in"], [">out"])for any type it doesn't list.BehaviorInputNodehas zero inputs and four outputs —Active,Behavior,On Enter,On Exit. So the fallback drew the wrong shape, andOn Enter(output index 2) was unreachable: a connection drawn on the canvas would have wiredActiveinstead. The exact connection #131's integration test makes programmatically could not be drawn by hand.Maimugets an entry in the same table, which only relabels its ports fromin/outtoexec/exec— its shape was already right by luck.The behavior is picked, not typed
A mistyped label is silently inert. The node compares the emitted string against the configured one, so
"freezng"simply never matches: the stimulus never fires and nothing anywhere says why. That is the worst failure mode available to a closed-loop stimulus, so the properties panel offers a dropdown of the loaded model's own vocabulary.It stays an editable combo on purpose:
How the vocabulary gets there. Over
LiveSignalBus, which is already the boundary between vision and the flow. The camera panel publishes the model's classes when inference goes live — the same place it already hands them to the preview overlay. The alternative, having the properties panel reach into the camera panel for a worker thread's attribute, would couple the flow editor to the vision UI's internals.Also
BehaviorInputin theinterfacecategory, so it styles like the other trigger nodesTests — 15 new
tests/unit/gui/test_behavior_input_editor.py: the palette offers the button; the ports are the node's real four withOn Enterat index 2 andActiveas data not exec; Maimu no longer takes the generic fallback; the dropdown is populated from the model; a saved behavior shows; a label saved before the model loaded survives; choosing and editing both persist; the panel opens with no bus at all (vision not running); the bus hands back a copy rather than its own list; and loading a model publishes its classes to the bus.Verification
PYTHONPATH=src QT_QPA_PLATFORM=offscreen pytest tests/— 3642 passed, 3 skipped, ruff and black clean.