Say a rebuild has two scopes, and close the notepad items it fixes

The module docstrings and the notepad still described one rebuild that
touches everything. Closes the toggle cost, the seeding cost, the
per-save rebuild, the modules/apply rebuild and the Playwright spec that
could not fit a rebuild into its five seconds; files the follow-ups the
refactor leaves behind.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01StpRc2C6au1WJ1EUU7fsfu
This commit is contained in:
2026-08-23 19:05:23 +02:00
co-authored by Claude Opus 5
parent 0a018fb698
commit 8d49a424f8
3 changed files with 43 additions and 24 deletions
+12 -6
View File
@@ -1,9 +1,15 @@
"""Turns stored flows into a running pipeline.
The controller is the only thing that builds nodes: it reads flow definitions
from the store, instantiates each node from its type, and rebuilds the shared
pipeline. A node that fails to load is reported and skipped — one broken node
never stops the rest.
from the store, instantiates each node from its type, and puts them in the
shared pipeline. A node that fails to load is reported and skipped — one
broken node never stops the rest.
Two scopes: `reload` builds every flow, and `reload_flow` swaps one flow's
nodes into the graph and leaves every other flow connected. Deploying takes
the second — the cost of a rebuild is each node opening its socket again, and
one flow's are all that changed. Starting one is neither: a stopped flow's
nodes are built like any other's, so `set_enabled` only starts or stops them.
"""
from __future__ import annotations
@@ -521,9 +527,9 @@ class FlowController:
flow.name: _declared_inputs(flow)[0] for flow in published
}
# A rebuild is a fresh set of nodes, but not a fresh history: every
# publish rebuilds every flow, so dropping the failures here would
# wipe them constantly. They are the operator's to dismiss.
# A rebuild is a fresh set of nodes, but not a fresh history:
# dropping the failures here would wipe them on every rebuild,
# and they are the operator's to dismiss.
for node_id, entry in loaded.items():
previous = self.loaded.get(node_id)
if previous is not None and previous.last_error:
+5
View File
@@ -4,6 +4,11 @@ One pipeline holds the nodes of every loaded flow. Edges are not declared —
they follow from message names, so a node consuming ``heating.setpoint`` is
downstream of every node providing it. Several producers of one message are
allowed: each publication triggers the consumers, and the latest value wins.
Because the wiring is derived rather than declared, one flow's nodes can be
swapped in place: `replace_flow` splices them into the list and derives the
whole map again, which gets the edges crossing into other flows right by
construction. A deploy does that rather than building a second pipeline.
"""
from __future__ import annotations