Summary
The domain metadata registry carries UI presentation concerns and reports configuration defects inconsistently with the rest of the config pipeline. Three related findings in one class.
1. UI combo-box concerns live in Core
Recipes/ComboBoxItemViewModel.cs:3 — a type named and shaped for Avalonia ComboBox binding sits in the Core domain layer.
Recipes/RecipeMetadataRegistry.cs:21-24, 177-205 — GetComboBoxItems/GetActionComboBoxItems caches whose XML docs reason about "UI bindings (per-row dictionaries on RecipeRowViewModel)" and "the cell factory". The registry even bends its own error contract for the binding pipeline: "Diverges from the Result pattern ... so that UI bindings ... can hold an empty-but-valid reference" (172-176).
Core has no Avalonia reference (good), but the concept leaked: UI presentation changes force Core edits.
Fix: move ComboBoxItemViewModel and both cache methods into SemiStep.UI (e.g. a provider in RecipeGrid) built on the existing Core primitives GetGroup/GetAllActions. The registry keeps one consistent Result-based contract.
2. Config defects throw where the rest of the pipeline returns Result
RecipeMetadataRegistry.cs:81, 107, 224, 244, 257, 274 — six InvalidOperationException throws in the constructor path for configuration defects (unresolved column/property references, string max-length rules). The same class of defects is reported via Result by Configuration/Validation/*. A bad config slipping past validators crashes DI container resolution instead of producing the aggregated report shown in ErrorWindow.
Fix: move EnsureColumnPropertyReferencesResolve and ResolveStringMaxLength rules into CrossReferenceValidator/DefaultValueValidator so all verdicts flow through ConfigFacade; keep constructor throws only as assertions of already-validated input.
3. Inconsistent lazy-cache thread safety
RecipeMetadataRegistry.cs:21-24, 202 — _comboItemsByGroup is a ConcurrentDictionary with a comment justifying concurrent access, yet _actionComboBoxItems ??= ... two members later is a plain non-thread-safe lazy field. Benign race (worst case a duplicate list), but the class contradicts its own stated concurrency model. Disappears if finding 1 is fixed.
Summary
The domain metadata registry carries UI presentation concerns and reports configuration defects inconsistently with the rest of the config pipeline. Three related findings in one class.
1. UI combo-box concerns live in Core
Recipes/ComboBoxItemViewModel.cs:3— a type named and shaped for Avalonia ComboBox binding sits in the Core domain layer.Recipes/RecipeMetadataRegistry.cs:21-24, 177-205—GetComboBoxItems/GetActionComboBoxItemscaches whose XML docs reason about "UI bindings (per-row dictionaries on RecipeRowViewModel)" and "the cell factory". The registry even bends its own error contract for the binding pipeline: "Diverges from the Result pattern ... so that UI bindings ... can hold an empty-but-valid reference" (172-176).Core has no Avalonia reference (good), but the concept leaked: UI presentation changes force Core edits.
Fix: move
ComboBoxItemViewModeland both cache methods intoSemiStep.UI(e.g. a provider inRecipeGrid) built on the existing Core primitivesGetGroup/GetAllActions. The registry keeps one consistent Result-based contract.2. Config defects throw where the rest of the pipeline returns Result
RecipeMetadataRegistry.cs:81, 107, 224, 244, 257, 274— sixInvalidOperationExceptionthrows in the constructor path for configuration defects (unresolved column/property references, string max-length rules). The same class of defects is reported viaResultbyConfiguration/Validation/*. A bad config slipping past validators crashes DI container resolution instead of producing the aggregated report shown inErrorWindow.Fix: move
EnsureColumnPropertyReferencesResolveandResolveStringMaxLengthrules intoCrossReferenceValidator/DefaultValueValidatorso all verdicts flow throughConfigFacade; keep constructor throws only as assertions of already-validated input.3. Inconsistent lazy-cache thread safety
RecipeMetadataRegistry.cs:21-24, 202—_comboItemsByGroupis aConcurrentDictionarywith a comment justifying concurrent access, yet_actionComboBoxItems ??= ...two members later is a plain non-thread-safe lazy field. Benign race (worst case a duplicate list), but the class contradicts its own stated concurrency model. Disappears if finding 1 is fixed.