diff --git a/LEARNINGS.md b/LEARNINGS.md index 6003836..d3d34af 100644 --- a/LEARNINGS.md +++ b/LEARNINGS.md @@ -527,3 +527,6 @@ Es gibt keinen `BLENDER_CINEMATIC`-Node im Workflow-Graph-System. Cinematic-Rend ### 2026-07-22 | Frontend | Editierbares Branding — public settings endpoint vor Auth nötig Der Admin-Settings-Endpunkt (`GET /api/admin/settings`) erfordert `global_admin`-Auth. Die Login-Seite benötigt den App-Namen aber vor dem Login. Lösung: eigener öffentlicher `GET /api/admin/branding`-Endpunkt ohne Auth-Dependency, der nur `app_name` + `app_subtitle` aus `system_settings` liest. Frontend: `useBranding()`-Hook via React Query mit 5-min-Stale-Time — Query-Key `['branding']` wird nach dem Speichern aus dem Admin-Panel invalidiert, damit Sidebar und Login-Seite sofort aktualisieren. + +### 2026-07-22 | Workflow-Editor | Blueprint-Workflows waren nach jedem Speichern read-only +`canonicalize_workflow_config` in `workflow_config_utils.py` hat bei jedem Aufruf Configs mit `ui.blueprint` (z.B. `still_graph_reference`, `order_rendering`) und `ui.preset == "still_graph"` auf das kanonische Template zurückgebaut — alle Node-Parameter-Änderungen und Strukturänderungen des Users wurden lautlos verworfen. Das betraf Update, Preflight und Execution-Dispatch gleichermaßen. **Lösung:** Parameter `preserve_user_graph: bool = False` — bei `True` werden die Rebuild-Blöcke übersprungen; nur Basic-Normalisierung läuft. `update_workflow`, `_workflow_to_out`, Preflight und Execution-Dispatch rufen jetzt mit `preserve_user_graph=True` auf. `create_workflow` bleibt bei `False` (Blueprints werden beim Erstellen korrekt expandiert). diff --git a/backend/app/domains/rendering/workflow_config_utils.py b/backend/app/domains/rendering/workflow_config_utils.py index 858a122..3baa20d 100644 --- a/backend/app/domains/rendering/workflow_config_utils.py +++ b/backend/app/domains/rendering/workflow_config_utils.py @@ -512,7 +512,7 @@ def _canonicalize_legacy_custom_config(raw: dict[str, Any]) -> dict[str, Any]: return canonical -def canonicalize_workflow_config(raw: dict[str, Any]) -> dict[str, Any]: +def canonicalize_workflow_config(raw: dict[str, Any], *, preserve_user_graph: bool = False) -> dict[str, Any]: if not isinstance(raw, dict): raise ValueError("Workflow config must be a JSON object") @@ -524,32 +524,34 @@ def canonicalize_workflow_config(raw: dict[str, Any]) -> dict[str, Any]: ui = {} normalized["ui"] = dict(ui) normalized["ui"].setdefault("execution_mode", "legacy") - preset = normalized["ui"].get("preset") - blueprint = normalized["ui"].get("blueprint") - if preset == "still_graph": - merged_ui = dict(normalized["ui"]) - canonical = build_preset_workflow_config( - "still_graph", - _extract_render_params_from_nodes(normalized.get("nodes") or [], StepName.BLENDER_STILL), - ) - merged_ui.setdefault("execution_mode", canonical["ui"]["execution_mode"]) - canonical["ui"].update(merged_ui) - return canonical + if not preserve_user_graph: + preset = normalized["ui"].get("preset") + blueprint = normalized["ui"].get("blueprint") - if blueprint in _WORKFLOW_BLUEPRINTS: - merged_ui = dict(normalized["ui"]) - canonical = build_workflow_blueprint_config(blueprint) - merged_ui.setdefault("execution_mode", canonical["ui"]["execution_mode"]) - canonical["ui"].update(merged_ui) - return canonical + if preset == "still_graph": + merged_ui = dict(normalized["ui"]) + canonical = build_preset_workflow_config( + "still_graph", + _extract_render_params_from_nodes(normalized.get("nodes") or [], StepName.BLENDER_STILL), + ) + merged_ui.setdefault("execution_mode", canonical["ui"]["execution_mode"]) + canonical["ui"].update(merged_ui) + return canonical - if blueprint in _WORKFLOW_STARTER_BLUEPRINTS: - merged_ui = dict(normalized["ui"]) - canonical = build_starter_workflow_config(_WORKFLOW_STARTER_BLUEPRINTS[blueprint]) - merged_ui.setdefault("execution_mode", canonical["ui"]["execution_mode"]) - canonical["ui"].update(merged_ui) - return canonical + if blueprint in _WORKFLOW_BLUEPRINTS: + merged_ui = dict(normalized["ui"]) + canonical = build_workflow_blueprint_config(blueprint) + merged_ui.setdefault("execution_mode", canonical["ui"]["execution_mode"]) + canonical["ui"].update(merged_ui) + return canonical + + if blueprint in _WORKFLOW_STARTER_BLUEPRINTS: + merged_ui = dict(normalized["ui"]) + canonical = build_starter_workflow_config(_WORKFLOW_STARTER_BLUEPRINTS[blueprint]) + merged_ui.setdefault("execution_mode", canonical["ui"]["execution_mode"]) + canonical["ui"].update(merged_ui) + return canonical return normalized diff --git a/backend/app/domains/rendering/workflow_router.py b/backend/app/domains/rendering/workflow_router.py index 5818702..43fdeef 100644 --- a/backend/app/domains/rendering/workflow_router.py +++ b/backend/app/domains/rendering/workflow_router.py @@ -264,7 +264,7 @@ async def _build_rollout_summary( async def _workflow_to_out(db: AsyncSession, wf: WorkflowDefinition) -> WorkflowDefinitionOut: - canonical_config = canonicalize_workflow_config(wf.config) + canonical_config = canonicalize_workflow_config(wf.config, preserve_user_graph=True) workflow_family = infer_workflow_family_from_config(canonical_config) supported_artifact_kinds = tuple( derive_supported_artifact_kinds_from_workflow_config(canonical_config) @@ -408,7 +408,7 @@ def _build_workflow_preflight_for_config( submit_prepared_workflow_tasks, ) - normalized_config = canonicalize_workflow_config(workflow_config) + normalized_config = canonicalize_workflow_config(workflow_config, preserve_user_graph=True) try: workflow_context = prepare_workflow_context( normalized_config, @@ -942,7 +942,7 @@ async def update_workflow( wf.name = body.name if body.config is not None: try: - normalized_config = canonicalize_workflow_config(body.config) + normalized_config = canonicalize_workflow_config(body.config, preserve_user_graph=True) WorkflowConfig.model_validate(normalized_config) except (ValidationError, ValueError) as exc: detail = exc.errors() if isinstance(exc, ValidationError) else str(exc) @@ -1033,7 +1033,7 @@ async def _dispatch_workflow_for_config( ) try: - normalized_config = canonicalize_workflow_config(workflow_config) + normalized_config = canonicalize_workflow_config(workflow_config, preserve_user_graph=True) workflow_context = prepare_workflow_context( normalized_config, context_id=context_id,