Publish, delete and module installs rebuild only what changed

Publishing or deleting a flow now splices that one flow into the running
graph instead of reconnecting every node in the installation, saving a
shared node's source rebuilds the flows using it, and installing modules
rebuilds only the flows holding a node that would not load.

Renaming stays on the full rebuild — it rewrites message references in
every other flow's document — and so does startup, which has no graph to
splice into.

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 18:32:51 +02:00
co-authored by Claude Opus 5
parent d553fbad80
commit cba8ca2c54
3 changed files with 16 additions and 4 deletions
+6
View File
@@ -10,6 +10,12 @@ Deferring because out of scope is fine, but don't mention deferring than.
## Open ## Open
### Dashboard UI rework (dedicated session)
Rework the dashboard UI to make it more flexible in terms of the look and feel.
I consider following design options a) overall appearance: material look or liquid glass (einUI https://github.com/einui/einui) b) color palette
### To be sorted ### To be sorted
- FEAT/UI: we promise testing, but currently don't provide an UI for testing e.g. mock values or probing edge cases of a flow. This should be resolved (in a dedicated session); I'm thinking of a "Labs" page, which allows simulating an installation with all the flows (using their draft states) and which allows injecting values or mocking values based on events in the past - FEAT/UI: we promise testing, but currently don't provide an UI for testing e.g. mock values or probing edge cases of a flow. This should be resolved (in a dedicated session); I'm thinking of a "Labs" page, which allows simulating an installation with all the flows (using their draft states) and which allows injecting values or mocking values based on events in the past
+3 -3
View File
@@ -425,7 +425,7 @@ async def publish_flow(
detail={"message": str(exc), "current_version": exc.current}, detail={"message": str(exc), "current_version": exc.current},
) )
_audit("published", name, user) _audit("published", name, user)
await controller.reload() await controller.reload_flow(name)
return _detail(controller, published) return _detail(controller, published)
@@ -475,7 +475,7 @@ async def delete_flow(
# Its files are gone; its values and queued work would otherwise linger. # Its files are gone; its values and queued work would otherwise linger.
await run_in_threadpool(controller.forget_flow, name) await run_in_threadpool(controller.forget_flow, name)
await run_in_threadpool(_forget_runs, session, name) await run_in_threadpool(_forget_runs, session, name)
await controller.reload() await controller.reload_flow(name)
return Message(message=f"Deleted flow '{name}'") return Message(message=f"Deleted flow '{name}'")
@@ -556,7 +556,7 @@ async def save_node_source(
controller.store.write_lib_source, ref, source.code controller.store.write_lib_source, ref, source.code
) )
if changed: if changed:
await controller.reload() await controller.reload_lib_users(ref)
else: else:
await run_in_threadpool( await run_in_threadpool(
controller.store.write_node_source, name, node_id, source.code, True controller.store.write_node_source, name, node_id, source.code, True
+7 -1
View File
@@ -44,6 +44,12 @@ async def apply_modules(
A manifest that does not resolve changes nothing: the venv is left as it A manifest that does not resolve changes nothing: the venv is left as it
was and the stored manifest is only written once the install succeeded. was and the stored manifest is only written once the install succeeded.
Only the flows already holding a node that would not load are rebuilt,
because those are the ones an install is called to fix. A flow that this
install *breaks* — a package taken back out from under it — is still green
and fails at call time with the node author's own import error, until
something rebuilds it.
""" """
ok, output = await run_in_threadpool(modules.sync, body.requirements) ok, output = await run_in_threadpool(modules.sync, body.requirements)
if not ok: if not ok:
@@ -65,5 +71,5 @@ async def apply_modules(
# packages that were just installed — a node that could not import one is # packages that were just installed — a node that could not import one is
# the reason this was called, and it stays red until it is built again. # the reason this was called, and it stays red until it is built again.
pool.respawn_all() pool.respawn_all()
await controller.reload() await controller.reload_failed_flows()
return ApplyResult(ok=True, output=output) return ApplyResult(ok=True, output=output)