From 5e0055f751d2aa8001853d75ea8b28becff79f1b Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 20:18:24 -0500 Subject: [PATCH 01/32] refactor(FiltersModalViewModel): dedupe BuildConfigFromCurrentState The Must/Should/MustNot construction was duplicated three times with shadowed pattern-match names (visualVm, visualVm2, visualVm3), one cast per branch. Extract three helpers and cast once. - AppendVisualClauses: walks FilterItem source, logs, skips nulls - AppendBannedItemsAsMustNot: unwraps BannedItems wrapper into MustNot - AppendKeyedClauses: ItemConfigs keyed-lookup path Behavior preserved: ConvertFilterItemToClause already returns null for BannedItems operators, so they remain skipped from Must/Should and are surfaced separately into MustNot via the unwrap helper, matching the existing JSON config schema. Method shrinks from 130 lines to 46; total file -15 lines. --- .../ViewModels/FiltersModalViewModel.cs | 155 ++++++++---------- 1 file changed, 70 insertions(+), 85 deletions(-) diff --git a/src/BalatroSeedOracle/ViewModels/FiltersModalViewModel.cs b/src/BalatroSeedOracle/ViewModels/FiltersModalViewModel.cs index e0f9db4..a51332a 100644 --- a/src/BalatroSeedOracle/ViewModels/FiltersModalViewModel.cs +++ b/src/BalatroSeedOracle/ViewModels/FiltersModalViewModel.cs @@ -901,7 +901,6 @@ private List GeneratePlayingCardsList() /// public Motely.Filters.MotelyJsonConfig BuildConfigFromCurrentState() { - // Get author from UserProfileService var userProfileService = ServiceHelper.GetService(); var author = userProfileService?.GetAuthorName() ?? "Unknown"; @@ -912,7 +911,6 @@ public Motely.Filters.MotelyJsonConfig BuildConfigFromCurrentState() // Preserve original DateCreated and Author when re-saving an existing filter DateCreated = _originalDateCreated ?? DateTime.Now, Author = _originalAuthor ?? author, - // Use enum ToString() for JSON serialization Deck = SelectedDeck.ToString(), Stake = SelectedStake.ToString().ToLower(), Must = new List(), @@ -920,114 +918,101 @@ public Motely.Filters.MotelyJsonConfig BuildConfigFromCurrentState() MustNot = new List(), }; - // CRITICAL FIX: Read from VisualBuilderTab's collections if available - // The VisualBuilderTab has its own SelectedMust/Should/MustNot collections (FilterItem objects) + var visualBuilder = VisualBuilderTab as FilterTabs.VisualBuilderTabViewModel; + BsoLogger.LogImportant( "FiltersModalViewModel", - $"🔍 BuildConfig: VisualBuilderTab={VisualBuilderTab?.GetType().Name ?? "NULL"}" + $"🔍 BuildConfig: VisualBuilderTab={visualBuilder?.GetType().Name ?? "NULL"}" ); - if (VisualBuilderTab is FilterTabs.VisualBuilderTabViewModel visualVm) + if (visualBuilder is not null) { BsoLogger.LogImportant( "FiltersModalViewModel", - $"✅ USING VisualBuilder PATH: {visualVm.SelectedMust.Count} must, {visualVm.SelectedShould.Count} should" + $"✅ USING VisualBuilder PATH: {visualBuilder.SelectedMust.Count} must, {visualBuilder.SelectedShould.Count} should" ); - // Build Must clauses directly from FilterItem objects (including FilterOperatorItems) - foreach (var filterItem in visualVm.SelectedMust) - { - BsoLogger.LogImportant( - "FiltersModalViewModel", - $"Processing MUST item: Name={filterItem.Name}, Type={filterItem.Type}, ActualType={filterItem.GetType().Name}" - ); - - var clause = ConvertFilterItemToClause(filterItem); - if (clause is not null) - { - config.Must.Add(clause); - BsoLogger.Log( - "FiltersModalViewModel", - $"Added clause: Type={clause.Type}, HasClauses={clause.Clauses is not null}, ClausesCount={clause.Clauses?.Count ?? 0}" - ); - } - else - { - BsoLogger.LogError( - "FiltersModalViewModel", - $"Failed to convert {filterItem.Name} to clause!" - ); - } - } + AppendVisualClauses(visualBuilder.SelectedMust, config.Must); + AppendVisualClauses(visualBuilder.SelectedShould, config.Should); + AppendBannedItemsAsMustNot(visualBuilder.SelectedMust, config.MustNot); } else { - // Fallback to parent's key-based collections - foreach (var itemKey in SelectedMust) - { - if (ItemConfigs.TryGetValue(itemKey, out var itemConfig)) - { - var clause = ConvertItemConfigToClause(itemConfig); - if (clause is not null) - config.Must.Add(clause); - } - } + AppendKeyedClauses(SelectedMust, config.Must); + AppendKeyedClauses(SelectedShould, config.Should); + AppendKeyedClauses(SelectedMustNot, config.MustNot); } - // Build Should clauses - if (VisualBuilderTab is FilterTabs.VisualBuilderTabViewModel visualVm2) - { - foreach (var filterItem in visualVm2.SelectedShould) - { - var clause = ConvertFilterItemToClause(filterItem); - if (clause is not null) - config.Should.Add(clause); - } - } - else + return config; + } + + // ConvertFilterItemToClause returns null for BannedItems operators by design, + // so they're skipped here and surfaced via AppendBannedItemsAsMustNot. + private void AppendVisualClauses( + IEnumerable source, + List target + ) + { + foreach (var filterItem in source) { - foreach (var itemKey in SelectedShould) + BsoLogger.LogImportant( + "FiltersModalViewModel", + $"Processing item: Name={filterItem.Name}, Type={filterItem.Type}, ActualType={filterItem.GetType().Name}" + ); + + var clause = ConvertFilterItemToClause(filterItem); + if (clause is null) { - if (ItemConfigs.TryGetValue(itemKey, out var itemConfig)) - { - var clause = ConvertItemConfigToClause(itemConfig); - if (clause is not null) - config.Should.Add(clause); - } + BsoLogger.LogError( + "FiltersModalViewModel", + $"Failed to convert {filterItem.Name} to clause!" + ); + continue; } + + target.Add(clause); + BsoLogger.Log( + "FiltersModalViewModel", + $"Added clause: Type={clause.Type}, HasClauses={clause.Clauses is not null}, ClausesCount={clause.Clauses?.Count ?? 0}" + ); } + } - // Build MustNot clauses - handle both BannedItems operator and direct MustNot - if (VisualBuilderTab is FilterTabs.VisualBuilderTabViewModel visualVm3) + // BannedItems live inside the Must collection as a FilterOperatorItem wrapper; + // unwrap their children into MustNot to match the JSON config schema. + private void AppendBannedItemsAsMustNot( + IEnumerable source, + List mustNot + ) + { + foreach (var item in source) { - // Check for BannedItems operator in Must collection - foreach (var item in visualVm3.SelectedMust) + if (item is not Models.FilterOperatorItem op || op.OperatorType != "BannedItems") + continue; + + foreach (var child in op.Children) { - if (item is Models.FilterOperatorItem op && op.OperatorType == "BannedItems") - { - foreach (var child in op.Children) - { - var clause = ConvertFilterItemToClause(child); - if (clause is not null) - config.MustNot.Add(clause); - } - } + var clause = ConvertFilterItemToClause(child); + if (clause is not null) + mustNot.Add(clause); } } - else + } + + private void AppendKeyedClauses( + IEnumerable itemKeys, + List target + ) + { + foreach (var itemKey in itemKeys) { - foreach (var itemKey in SelectedMustNot) - { - if (ItemConfigs.TryGetValue(itemKey, out var itemConfig)) - { - var clause = ConvertItemConfigToClause(itemConfig); - if (clause is not null) - config.MustNot.Add(clause); - } - } - } + if (!ItemConfigs.TryGetValue(itemKey, out var itemConfig)) + continue; - return config; + var clause = ConvertItemConfigToClause(itemConfig); + if (clause is not null) + target.Add(clause); + } } private MotelyJsonConfig.MotelyJsonFilterClause? ConvertItemConfigToClause( From d943cc3a2457bf6736e620691163ced2e9dfb4f3 Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 20:23:53 -0500 Subject: [PATCH 02/32] refactor(FiltersModalViewModel): dedupe LoadConfigIntoState clause loading LoadConfigIntoState had three near-identical foreach blocks for Must/Should/MustNot. Each: - null-checked the source list - iterated each clause - generated a fresh ItemConfigs key - converted the clause via ConvertClauseToItemConfig - registered in ItemConfigs and appended the key to the matching Selected* ObservableCollection Replace with a single LoadClausesIntoSelection helper invoked three times. Behavior identical: same null guard, same key generation order, same conversion call, same target collections. -14 lines. --- .../ViewModels/FiltersModalViewModel.cs | 56 +++++++------------ 1 file changed, 21 insertions(+), 35 deletions(-) diff --git a/src/BalatroSeedOracle/ViewModels/FiltersModalViewModel.cs b/src/BalatroSeedOracle/ViewModels/FiltersModalViewModel.cs index a51332a..738f5bf 100644 --- a/src/BalatroSeedOracle/ViewModels/FiltersModalViewModel.cs +++ b/src/BalatroSeedOracle/ViewModels/FiltersModalViewModel.cs @@ -1274,41 +1274,9 @@ public void LoadConfigIntoState(Motely.Filters.MotelyJsonConfig config) SelectedStake = stake; } - // Load Must clauses - if (config.Must is not null) - { - foreach (var clause in config.Must) - { - var itemKey = GenerateNextItemKey(); - var itemConfig = ConvertClauseToItemConfig(clause, itemKey); - ItemConfigs[itemKey] = itemConfig; - SelectedMust.Add(itemKey); - } - } - - // Load Should clauses - if (config.Should is not null) - { - foreach (var clause in config.Should) - { - var itemKey = GenerateNextItemKey(); - var itemConfig = ConvertClauseToItemConfig(clause, itemKey); - ItemConfigs[itemKey] = itemConfig; - SelectedShould.Add(itemKey); - } - } - - // Load MustNot clauses - if (config.MustNot is not null) - { - foreach (var clause in config.MustNot) - { - var itemKey = GenerateNextItemKey(); - var itemConfig = ConvertClauseToItemConfig(clause, itemKey); - ItemConfigs[itemKey] = itemConfig; - SelectedMustNot.Add(itemKey); - } - } + LoadClausesIntoSelection(config.Must, SelectedMust); + LoadClausesIntoSelection(config.Should, SelectedShould); + LoadClausesIntoSelection(config.MustNot, SelectedMustNot); LoadedConfig = config; @@ -1324,6 +1292,24 @@ public void LoadConfigIntoState(Motely.Filters.MotelyJsonConfig config) } } + // Each clause is registered in ItemConfigs under a freshly generated key, + // and the key is appended to the appropriate selection collection. + private void LoadClausesIntoSelection( + IEnumerable? clauses, + ObservableCollection selection + ) + { + if (clauses is null) + return; + + foreach (var clause in clauses) + { + var itemKey = GenerateNextItemKey(); + ItemConfigs[itemKey] = ConvertClauseToItemConfig(clause, itemKey); + selection.Add(itemKey); + } + } + private ItemConfig ConvertClauseToItemConfig( MotelyJsonConfig.MotelyJsonFilterClause clause, string itemKey From f659ed42f1b3143fad7e684c3f682a980bf08a45 Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 20:45:41 -0500 Subject: [PATCH 03/32] ci(build-browser): fetch submodules so dotnet restore can resolve src/MotelyJAML MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The repo declares src/MotelyJAML as a git submodule (.gitmodules), but actions/checkout@v4 only fetches it when given submodules: true|recursive. Without that, dotnet restore runs against an empty src/MotelyJAML/ and exits 1 before any C# is compiled — which is what was happening on PR #20. The fix is the same one already applied to release-windows/linux/macos and deploy-browser.yml. This brings build-browser.yml in line with them. Pre-existing breakage; not introduced by the FiltersModalViewModel refactor. --- .github/workflows/build-browser.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index bc79e57..32296bc 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -19,6 +19,8 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v4 + with: + submodules: recursive - name: Setup .NET SDK uses: actions/setup-dotnet@v4 From 0f84159802509496077f41a593dc85dd952fdeb4 Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 20:45:51 -0500 Subject: [PATCH 04/32] ci(release-browser): fetch submodules to match other release workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same MotelyJAML submodule issue as build-browser.yml — release-browser.yml already had fetch-depth: 0 but not submodules: recursive, so dotnet restore would die on tags. Brings parity with release-windows/linux/macos. --- .github/workflows/release-browser.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release-browser.yml b/.github/workflows/release-browser.yml index 2bd16c9..3b6c095 100644 --- a/.github/workflows/release-browser.yml +++ b/.github/workflows/release-browser.yml @@ -17,6 +17,7 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 with: + submodules: recursive fetch-depth: 0 - name: Setup .NET SDK From ead7d3dd679adc842272eca12538e1b49287f39f Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 21:21:15 -0500 Subject: [PATCH 05/32] refactor(FiltersModalViewModel): dedupe deck/stake arrays in CreateLoadTabContent CreateLoadTabContent inlined the same 15-element deck name array twice and the same 8-element lowercase stake name array twice (~75 lines total of copy-paste, four sources of drift across one method). Changes: - Add private static readonly StakeNamesByIndex (already had DeckDisplayValues derived from Enum.GetNames at the class level, so reuse that for decks) - Add private ApplyDeckFromConfig / ApplyStakeFromConfig helpers - Replace all four inline blocks with single-line helper calls - Remove dead-code: GetDeckName(int) and GetStakeName(int) had zero callers in the file (private methods, not virtual, not [JsonInclude]'d, not used by source generators) -81 lines net. File: 2043 -> 1962. Cumulative across 3 file commits: -110 lines from the original 2072. --- .../ViewModels/FiltersModalViewModel.cs | 145 ++++-------------- 1 file changed, 32 insertions(+), 113 deletions(-) diff --git a/src/BalatroSeedOracle/ViewModels/FiltersModalViewModel.cs b/src/BalatroSeedOracle/ViewModels/FiltersModalViewModel.cs index 738f5bf..5b26bd3 100644 --- a/src/BalatroSeedOracle/ViewModels/FiltersModalViewModel.cs +++ b/src/BalatroSeedOracle/ViewModels/FiltersModalViewModel.cs @@ -184,6 +184,20 @@ public FiltersModalViewModel( public string[] DeckDisplayValues { get; } = Enum.GetNames(typeof(MotelyDeck)); public string[] StakeDisplayValues { get; } = Enum.GetNames(typeof(MotelyStake)); + // The stake-by-UI-index mapping is order-specific and uses lowercase JSON values + // (MotelyStake enum has gaps so we can't derive this from Enum.GetNames). + private static readonly string[] StakeNamesByIndex = + { + "white", + "red", + "green", + "black", + "blue", + "purple", + "orange", + "gold", + }; + // Deck/Stake index helpers public int SelectedDeckIndex { @@ -1533,53 +1547,8 @@ private object CreateLoadTabContent() await UpdateVisualBuilderFromItemConfigs(); ExpandDropZonesWithItems(); - // Update deck and stake selection indices - if (!string.IsNullOrEmpty(config.Deck)) - { - var deckIndex = Array.IndexOf( - new[] - { - "Red", - "Blue", - "Yellow", - "Green", - "Black", - "Magic", - "Nebula", - "Ghost", - "Abandoned", - "Checkered", - "Zodiac", - "Painted", - "Anaglyph", - "Plasma", - "Erratic", - }, - config.Deck - ); - if (deckIndex >= 0) - SelectedDeckIndex = deckIndex; - } - - if (!string.IsNullOrEmpty(config.Stake)) - { - var stakeIndex = Array.IndexOf( - new[] - { - "white", - "red", - "green", - "black", - "blue", - "purple", - "orange", - "gold", - }, - config.Stake.ToLower() - ); - if (stakeIndex >= 0) - SelectedStakeIndex = stakeIndex; - } + ApplyDeckFromConfig(config.Deck); + ApplyStakeFromConfig(config.Stake); // Switch to Visual Builder tab SelectedTabIndex = 1; @@ -1679,52 +1648,8 @@ private object CreateLoadTabContent() ExpandDropZonesWithItems(); // Preserve deck/stake selections from the copy - if (!string.IsNullOrEmpty(newConfig.Deck)) - { - var deckIndex = Array.IndexOf( - new[] - { - "Red", - "Blue", - "Yellow", - "Green", - "Black", - "Magic", - "Nebula", - "Ghost", - "Abandoned", - "Checkered", - "Zodiac", - "Painted", - "Anaglyph", - "Plasma", - "Erratic", - }, - newConfig.Deck - ); - if (deckIndex >= 0) - SelectedDeckIndex = deckIndex; - } - - if (!string.IsNullOrEmpty(newConfig.Stake)) - { - var stakeIndex = Array.IndexOf( - new[] - { - "white", - "red", - "green", - "black", - "blue", - "purple", - "orange", - "gold", - }, - newConfig.Stake.ToLower() - ); - if (stakeIndex >= 0) - SelectedStakeIndex = stakeIndex; - } + ApplyDeckFromConfig(newConfig.Deck); + ApplyStakeFromConfig(newConfig.Stake); SelectedTabIndex = 1; BsoLogger.Log( @@ -1799,30 +1724,24 @@ private object CreateLoadTabContent() return filterSelector; } - // Convert index to deck name via enum - private string GetDeckName(int index) + // Lookup deck/stake name from a config blob and apply to the SelectedXIndex setter. + // Both no-op if the name is missing or unrecognised, leaving the previous selection intact. + private void ApplyDeckFromConfig(string? deckName) { - if (index >= 0 && index <= 14) - return ((Motely.MotelyDeck)index).ToString(); - return "Red"; + if (string.IsNullOrEmpty(deckName)) + return; + var index = Array.IndexOf(DeckDisplayValues, deckName); + if (index >= 0) + SelectedDeckIndex = index; } - // Convert index to stake name via enum (handles gaps in enum values) - private string GetStakeName(int index) + private void ApplyStakeFromConfig(string? stakeName) { - var stake = index switch - { - 0 => MotelyStake.White, - 1 => MotelyStake.Red, - 2 => MotelyStake.Green, - 3 => MotelyStake.Black, - 4 => MotelyStake.Blue, - 5 => MotelyStake.Purple, - 6 => MotelyStake.Orange, - 7 => MotelyStake.Gold, - _ => MotelyStake.White, - }; - return stake.ToString().ToLower(); + if (string.IsNullOrEmpty(stakeName)) + return; + var index = Array.IndexOf(StakeNamesByIndex, stakeName.ToLower()); + if (index >= 0) + SelectedStakeIndex = index; } /// From c2b3bc6eef97dc3652b7dc2c76c6ab3c34f5f6c9 Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 21:28:18 -0500 Subject: [PATCH 06/32] build(Directory.Build.props): hoist AvaloniaUseCompiledBindingsByDefault MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per Avalonia's Native AOT guidance, compiled bindings are mandatory (not optional) when reflection is disabled. Desktop already sets IlcDisableReflection=true and Browser uses TrimMode=full, so any binding that falls back to reflection would crash at runtime, not just be slow. The shared BalatroSeedOracle.csproj sets the prop, but heads (Desktop, Browser, Android, iOS) don't inherit MSBuild props from referenced projects — only from Directory.Build.props upward. Hoisting guarantees every head's XAML compiler gets the same default. Pairs with the next commit which removes the now-redundant local prop from BalatroSeedOracle.csproj. --- Directory.Build.props | 85 ++++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index a754109..5687fcc 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,42 +1,51 @@ - - +<Project> + <!-- Centralized package versions are in Directory.Packages.props (MSBuild convention) --> - - - Balatro Seed Oracle - OptimusPi - Copyright 2025 - 2.0.0 - 2.0.0 - 2.0.0 - - false - + <!-- Shared Assembly Metadata --> + <PropertyGroup> + <Product>Balatro Seed Oracle</Product> + <Company>OptimusPi</Company> + <Copyright>Copyright 2025</Copyright> + <Version>2.0.0</Version> + <FileVersion>2.0.0</FileVersion> + <AssemblyVersion>2.0.0</AssemblyVersion> + <!-- Don't generate assembly info - let projects handle it --> + <GenerateAssemblyInfo>false</GenerateAssemblyInfo> + </PropertyGroup> - - - <_AvaloniaLicenseFile>$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)avalonia.license').Trim()) - <_AvaloniaLicenseKey Condition="'$(PARCEL_LICENSE)' != ''">$(PARCEL_LICENSE) - <_AvaloniaLicenseKey Condition="'$(_AvaloniaLicenseKey)' == '' AND '$(_AvaloniaLicenseFile)' != ''">$(_AvaloniaLicenseFile) - - - - + <!-- Avalonia License Key - from PARCEL_LICENSE env var OR avalonia.license file --> + <PropertyGroup> + <_AvaloniaLicenseFile>$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)avalonia.license').Trim())</_AvaloniaLicenseFile> + <_AvaloniaLicenseKey Condition="'$(PARCEL_LICENSE)' != ''">$(PARCEL_LICENSE)</_AvaloniaLicenseKey> + <_AvaloniaLicenseKey Condition="'$(_AvaloniaLicenseKey)' == '' AND '$(_AvaloniaLicenseFile)' != ''">$(_AvaloniaLicenseFile)</_AvaloniaLicenseKey> + </PropertyGroup> + <ItemGroup Condition="'$(_AvaloniaLicenseKey)' != ''"> + <AvaloniaUILicenseKey Include="$(_AvaloniaLicenseKey)" /> + </ItemGroup> - - - enable - true - Default - true - true - false - 4 - + <!-- AOT-mandatory: compiled bindings everywhere. + Desktop sets IlcDisableReflection=true and Browser uses TrimMode=full, + so any reflection-based binding would crash at runtime. Per Avalonia's + Native AOT guidance, this prop must apply across all heads, not just + the shared library. --> + <PropertyGroup> + <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault> + </PropertyGroup> - - - true - $(LOCALAPPDATA)\Android\android-sdk - - + <!-- Code Quality Settings --> + <PropertyGroup> + <Nullable>enable</Nullable> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + <AnalysisMode>Default</AnalysisMode> + <EnableNETAnalyzers>true</EnableNETAnalyzers> + <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild> + <TreatWarningsAsErrors>false</TreatWarningsAsErrors> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + + <!-- Android SDK Settings --> + <PropertyGroup Condition="$(TargetFramework.StartsWith('net10.0-android'))"> + <AcceptAndroidSDKLicenses>true</AcceptAndroidSDKLicenses> + <AndroidSdkDirectory>$(LOCALAPPDATA)\Android\android-sdk</AndroidSdkDirectory> + </PropertyGroup> +</Project> From a915f038a1dbe8f033ee3aa179e2577f1daf2f92 Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 21:28:49 -0500 Subject: [PATCH 07/32] fix(Directory.Build.props): repair HTML-encoded angle brackets Previous commit (c2b3bc6) accidentally serialized the XML with HTML entity encoding (< / >) instead of literal angle brackets, producing an unparseable MSBuild file. Re-push with raw XML. Same intent as before: hoist AvaloniaUseCompiledBindingsByDefault so the prop applies to all heads, not just the shared library, and pairs with the follow-up that removes the redundant local prop from the shared csproj. --- Directory.Build.props | 86 +++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 5687fcc..fab1aa2 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,51 +1,51 @@ -<Project> - <!-- Centralized package versions are in Directory.Packages.props (MSBuild convention) --> + + - <!-- Shared Assembly Metadata --> - <PropertyGroup> - <Product>Balatro Seed Oracle</Product> - <Company>OptimusPi</Company> - <Copyright>Copyright 2025</Copyright> - <Version>2.0.0</Version> - <FileVersion>2.0.0</FileVersion> - <AssemblyVersion>2.0.0</AssemblyVersion> - <!-- Don't generate assembly info - let projects handle it --> - <GenerateAssemblyInfo>false</GenerateAssemblyInfo> - </PropertyGroup> + + + Balatro Seed Oracle + OptimusPi + Copyright 2025 + 2.0.0 + 2.0.0 + 2.0.0 + + false + - <!-- Avalonia License Key - from PARCEL_LICENSE env var OR avalonia.license file --> - <PropertyGroup> - <_AvaloniaLicenseFile>$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)avalonia.license').Trim())</_AvaloniaLicenseFile> - <_AvaloniaLicenseKey Condition="'$(PARCEL_LICENSE)' != ''">$(PARCEL_LICENSE)</_AvaloniaLicenseKey> - <_AvaloniaLicenseKey Condition="'$(_AvaloniaLicenseKey)' == '' AND '$(_AvaloniaLicenseFile)' != ''">$(_AvaloniaLicenseFile)</_AvaloniaLicenseKey> - </PropertyGroup> - <ItemGroup Condition="'$(_AvaloniaLicenseKey)' != ''"> - <AvaloniaUILicenseKey Include="$(_AvaloniaLicenseKey)" /> - </ItemGroup> + + + <_AvaloniaLicenseFile>$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)avalonia.license').Trim()) + <_AvaloniaLicenseKey Condition="'$(PARCEL_LICENSE)' != ''">$(PARCEL_LICENSE) + <_AvaloniaLicenseKey Condition="'$(_AvaloniaLicenseKey)' == '' AND '$(_AvaloniaLicenseFile)' != ''">$(_AvaloniaLicenseFile) + + + + - <!-- AOT-mandatory: compiled bindings everywhere. + + + true + - <!-- Code Quality Settings --> - <PropertyGroup> - <Nullable>enable</Nullable> - <AllowUnsafeBlocks>true</AllowUnsafeBlocks> - <AnalysisMode>Default</AnalysisMode> - <EnableNETAnalyzers>true</EnableNETAnalyzers> - <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild> - <TreatWarningsAsErrors>false</TreatWarningsAsErrors> - <WarningLevel>4</WarningLevel> - </PropertyGroup> + + + enable + true + Default + true + true + false + 4 + - <!-- Android SDK Settings --> - <PropertyGroup Condition="$(TargetFramework.StartsWith('net10.0-android'))"> - <AcceptAndroidSDKLicenses>true</AcceptAndroidSDKLicenses> - <AndroidSdkDirectory>$(LOCALAPPDATA)\Android\android-sdk</AndroidSdkDirectory> - </PropertyGroup> -</Project> + + + true + $(LOCALAPPDATA)\Android\android-sdk + + From 4f2dcdfb5b2c5bf1f3059a4d1c20f99054d7bdd1 Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 21:29:27 -0500 Subject: [PATCH 08/32] build(BalatroSeedOracle.csproj): drop now-redundant AvaloniaUseCompiledBindingsByDefault The prop is now set in Directory.Build.props so every project (heads included) gets it. Keeping the duplicate here is just drift surface. --- src/BalatroSeedOracle/BalatroSeedOracle.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/BalatroSeedOracle/BalatroSeedOracle.csproj b/src/BalatroSeedOracle/BalatroSeedOracle.csproj index 5039541..39e4594 100644 --- a/src/BalatroSeedOracle/BalatroSeedOracle.csproj +++ b/src/BalatroSeedOracle/BalatroSeedOracle.csproj @@ -2,7 +2,6 @@ net10.0 - true BalatroSeedOracle From 53c7e630b415543d0cdbcdc024a57f46b279c337 Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 21:30:57 -0500 Subject: [PATCH 09/32] chore: drop publish_log.txt (build log accidentally committed) --- publish_log.txt | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 publish_log.txt diff --git a/publish_log.txt b/publish_log.txt deleted file mode 100644 index 3067188..0000000 --- a/publish_log.txt +++ /dev/null @@ -1,11 +0,0 @@ - Determining projects to restore... - Restored X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj (in 320 ms). - 3 of 4 projects are up-to-date for restore. - Motely -> X:\BalatroSeedOracle\external\Motely\Motely\bin\Release\net10.0\Motely.dll - Motely.Orchestration -> X:\BalatroSeedOracle\external\Motely\Motely.Orchestration\bin\Release\net10.0\Motely.Orchestration.dll -X:\BalatroSeedOracle\src\BalatroSeedOracle\Controls/SortableResultsGrid.axaml(115,10,115,10): Avalonia error AVLN2000: Unable to resolve suitable regular or attached property CanUserReorderColumns on type Avalonia.Controls.TreeDataGrid:Avalonia.Controls.TreeDataGrid Line 115, position 10. [X:\BalatroSeedOracle\src\BalatroSeedOracle\BalatroSeedOracle.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle\Controls/SortableResultsGrid.axaml(115,10,115,10): Avalonia error AVLN2000: Unable to resolve suitable regular or attached property GridLinesVisibility on type Avalonia.Controls.TreeDataGrid:Avalonia.Controls.TreeDataGrid Line 115, position 10. [X:\BalatroSeedOracle\src\BalatroSeedOracle\BalatroSeedOracle.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle\Controls/SortableResultsGrid.axaml(115,10,115,10): Avalonia error AVLN2000: Unable to resolve suitable regular or attached property HeadersVisibility on type Avalonia.Controls.TreeDataGrid:Avalonia.Controls.TreeDataGrid Line 115, position 10. [X:\BalatroSeedOracle\src\BalatroSeedOracle\BalatroSeedOracle.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle\Controls/SortableResultsGrid.axaml(115,10,115,10): Avalonia error AVLN2000: Unable to resolve suitable regular or attached property SelectionMode on type Avalonia.Controls.TreeDataGrid:Avalonia.Controls.TreeDataGrid Line 115, position 10. [X:\BalatroSeedOracle\src\BalatroSeedOracle\BalatroSeedOracle.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle\Controls/SortableResultsGrid.axaml(115,10,115,10): Avalonia error AVLN2000: Unable to resolve suitable regular or attached property RowBackground on type Avalonia.Controls.TreeDataGrid:Avalonia.Controls.TreeDataGrid Line 115, position 10. [X:\BalatroSeedOracle\src\BalatroSeedOracle\BalatroSeedOracle.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle\Controls/SortableResultsGrid.axaml(117,36,117,36): Avalonia error AVLN2000: Unable to resolve property or method of name 'TreeDataGridSource' on type 'BalatroSeedOracle.ViewModels.Controls.SortableResultsGridViewModel'. Line 117, position 36. [X:\BalatroSeedOracle\src\BalatroSeedOracle\BalatroSeedOracle.csproj] From fa7623d3a2c76a53e84c6a82dab98d2a2ecaa119 Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 21:30:58 -0500 Subject: [PATCH 10/32] chore: drop publish_retry.txt (build log accidentally committed) --- publish_retry.txt | 36 ------------------------------------ 1 file changed, 36 deletions(-) delete mode 100644 publish_retry.txt diff --git a/publish_retry.txt b/publish_retry.txt deleted file mode 100644 index 4c545f8..0000000 --- a/publish_retry.txt +++ /dev/null @@ -1,36 +0,0 @@ - Determining projects to restore... - All projects are up-to-date for restore. - Motely -> X:\BalatroSeedOracle\external\Motely\Motely\bin\Release\net10.0\Motely.dll - Motely.Orchestration -> X:\BalatroSeedOracle\external\Motely\Motely.Orchestration\bin\Release\net10.0\Motely.Orchestration.dll - BalatroSeedOracle -> X:\BalatroSeedOracle\src\BalatroSeedOracle\bin\Release\net10.0\BalatroSeedOracle.dll -X:\BalatroSeedOracle\external\Motely\Motely\filters\MotelyJson\MotelyJsonConfig.cs(1854,83): warning IL3050: Using member 'System.Enum.GetValues(Type)' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. It might not be possible to create an array of the enum type at runtime. Use the GetValues overload or the GetValuesAsUnderlyingType method instead. [X:\BalatroSeedOracle\external\Motely\Motely\Motely.csproj::TargetFramework=net10.0-browser] -X:\BalatroSeedOracle\external\Motely\Motely\filters\MotelyJson\MotelyJsonConfig.cs(1848,79): warning IL3050: Using member 'System.Enum.GetValues(Type)' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. It might not be possible to create an array of the enum type at runtime. Use the GetValues overload or the GetValuesAsUnderlyingType method instead. [X:\BalatroSeedOracle\external\Motely\Motely\Motely.csproj::TargetFramework=net10.0-browser] -X:\BalatroSeedOracle\external\Motely\Motely\filters\MotelyJson\JamlTypeAsKeyConverter.cs(44,20): warning IL2070: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicProperties', 'DynamicallyAccessedMemberTypes.NonPublicProperties' in call to 'System.Type.GetProperty(String, BindingFlags)'. The parameter 'type' of method 'Motely.Filters.MotelyJson.JamlTypeAsKeyNodeDeserializer.FindPropertyWithAlias(Type, String)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. [X:\BalatroSeedOracle\external\Motely\Motely\Motely.csproj::TargetFramework=net10.0-browser] -X:\BalatroSeedOracle\external\Motely\Motely\filters\MotelyJson\JamlFormatter.cs(100,26): warning IL3050: Using member 'YamlDotNet.Serialization.SerializerBuilder.SerializerBuilder()' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. This builder configures the serializer to use reflection which is not compatible with ahead-of-time compilation or assembly trimming. You need to use the code generator/analyzer to generate static code and use the 'StaticSerializerBuilder' object instead of this one. [X:\BalatroSeedOracle\external\Motely\Motely\Motely.csproj::TargetFramework=net10.0-browser] -X:\BalatroSeedOracle\external\Motely\Motely\filters\MotelyJson\MotelyRunSeedScoreDesc.cs(26,26): warning IL2026: Using member 'System.Text.Json.JsonSerializer.Deserialize(String, JsonSerializerOptions)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved. [X:\BalatroSeedOracle\external\Motely\Motely\Motely.csproj::TargetFramework=net10.0-browser] -X:\BalatroSeedOracle\external\Motely\Motely\filters\MotelyJson\MotelyRunSeedScoreDesc.cs(26,26): warning IL3050: Using member 'System.Text.Json.JsonSerializer.Deserialize(String, JsonSerializerOptions)' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications. [X:\BalatroSeedOracle\external\Motely\Motely\Motely.csproj::TargetFramework=net10.0-browser] -X:\BalatroSeedOracle\external\Motely\Motely\filters\MotelyJson\MotelyRunConfig.Serialization.cs(20,16): warning IL2026: Using member 'System.Text.Json.JsonSerializer.Serialize(TValue, JsonSerializerOptions)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved. [X:\BalatroSeedOracle\external\Motely\Motely\Motely.csproj::TargetFramework=net10.0-browser] -X:\BalatroSeedOracle\external\Motely\Motely\filters\MotelyJson\MotelyRunConfig.Serialization.cs(20,16): warning IL3050: Using member 'System.Text.Json.JsonSerializer.Serialize(TValue, JsonSerializerOptions)' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications. [X:\BalatroSeedOracle\external\Motely\Motely\Motely.csproj::TargetFramework=net10.0-browser] -X:\BalatroSeedOracle\external\Motely\Motely\filters\MotelyJson\JamlFormatter.cs(137,28): warning IL3050: Using member 'YamlDotNet.Serialization.DeserializerBuilder.DeserializerBuilder()' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. This builder configures the deserializer to use reflection which is not compatible with ahead-of-time compilation or assembly trimming. You need to use the code generator/analyzer to generate static code and use the 'StaticDeserializerBuilder' object instead of this one. [X:\BalatroSeedOracle\external\Motely\Motely\Motely.csproj::TargetFramework=net10.0-browser] -X:\BalatroSeedOracle\external\Motely\Motely\filters\MotelyJson\MotelyJsonConfig.cs(1084,38): warning IL2026: Using member 'System.Text.Json.JsonSerializer.Deserialize(String, JsonSerializerOptions)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved. [X:\BalatroSeedOracle\external\Motely\Motely\Motely.csproj::TargetFramework=net10.0-browser] -X:\BalatroSeedOracle\external\Motely\Motely\filters\MotelyJson\MotelyJsonConfig.cs(1084,38): warning IL3050: Using member 'System.Text.Json.JsonSerializer.Deserialize(String, JsonSerializerOptions)' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications. [X:\BalatroSeedOracle\external\Motely\Motely\Motely.csproj::TargetFramework=net10.0-browser] -X:\BalatroSeedOracle\external\Motely\Motely\filters\MotelyJson\MotelyJsonConfig.cs(1658,16): warning IL2026: Using member 'System.Text.Json.JsonSerializer.Serialize(TValue, JsonSerializerOptions)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved. [X:\BalatroSeedOracle\external\Motely\Motely\Motely.csproj::TargetFramework=net10.0-browser] -X:\BalatroSeedOracle\external\Motely\Motely\filters\MotelyJson\MotelyJsonConfig.cs(1658,16): warning IL3050: Using member 'System.Text.Json.JsonSerializer.Serialize(TValue, JsonSerializerOptions)' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications. [X:\BalatroSeedOracle\external\Motely\Motely\Motely.csproj::TargetFramework=net10.0-browser] -X:\BalatroSeedOracle\external\Motely\Motely.DB\SeedSourceProvider.cs(74,32): warning CA1416: This call site is reachable on: 'browser' 1.0 and later. 'Console.ReadLine()' is unsupported on: 'browser' all versions. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416) [X:\BalatroSeedOracle\external\Motely\Motely\Motely.csproj::TargetFramework=net10.0-browser] -X:\BalatroSeedOracle\external\Motely\Motely\filters\MotelyJson\JamlTypeAsKeyConverter.cs(206,27): warning IL2067: 'type' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicParameterlessConstructor' in call to 'System.Activator.CreateInstance(Type)'. The parameter 'expectedType' of method 'Motely.Filters.MotelyJson.JamlTypeAsKeyNodeDeserializer.Deserialize(IParser, Type, Func, out Object, ObjectDeserializer)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. [X:\BalatroSeedOracle\external\Motely\Motely\Motely.csproj::TargetFramework=net10.0-browser] -X:\BalatroSeedOracle\external\Motely\Motely\filters\MotelyJson\JamlTypeAsKeyConverter.cs(239,39): warning IL2075: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicProperties', 'DynamicallyAccessedMemberTypes.NonPublicProperties' in call to 'System.Type.GetProperty(String, BindingFlags)'. The return value of method 'System.Object.GetType()' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. [X:\BalatroSeedOracle\external\Motely\Motely\Motely.csproj::TargetFramework=net10.0-browser] - Motely -> X:\BalatroSeedOracle\external\Motely\Motely\bin\Release\net10.0-browser\Motely.dll -X:\BalatroSeedOracle\external\Motely\Motely.Orchestration\Executors\JsonSearchExecutor.cs(174,44): warning CS0219: The variable 'cancelHandler' is assigned but its value is never used [X:\BalatroSeedOracle\external\Motely\Motely.Orchestration\Motely.Orchestration.csproj::TargetFramework=net10.0-browser] -X:\BalatroSeedOracle\external\Motely\Motely.Orchestration\Executors\JsonSearchExecutor.cs(560,36): warning CA1416: This call site is reachable on: 'browser' 1.0 and later. 'Console.ReadLine()' is unsupported on: 'browser' all versions. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416) [X:\BalatroSeedOracle\external\Motely\Motely.Orchestration\Motely.Orchestration.csproj::TargetFramework=net10.0-browser] -X:\BalatroSeedOracle\external\Motely\Motely.Orchestration\Executors\JsonSearchExecutor.cs(444,36): warning CA1416: This call site is reachable on: 'browser' 1.0 and later. 'Console.ReadLine()' is unsupported on: 'browser' all versions. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416) [X:\BalatroSeedOracle\external\Motely\Motely.Orchestration\Motely.Orchestration.csproj::TargetFramework=net10.0-browser] - Motely.Orchestration -> X:\BalatroSeedOracle\external\Motely\Motely.Orchestration\bin\Release\net10.0-browser\Motely.Orchestration.dll -X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\Services\BrowserLocalStorageAppDataStore.cs(55,20): error CS0029: Cannot implicitly convert type 'System.Threading.Tasks.Task' to 'System.Threading.Tasks.ValueTask' [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\Services\BrowserLocalStorageAppDataStore.cs(60,20): error CS0029: Cannot implicitly convert type 'System.Threading.Tasks.Task' to 'System.Threading.Tasks.ValueTask' [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\Services\BrowserLocalStorageAppDataStore.cs(98,20): error CS0029: Cannot implicitly convert type 'System.Threading.Tasks.Task' to 'System.Threading.Tasks.ValueTask' [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\Services\BrowserLocalStorageAppDataStore.cs(130,20): error CS0029: Cannot implicitly convert type 'System.Threading.Tasks.Task>' to 'System.Threading.Tasks.ValueTask>' [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\Services\BrowserLocalStorageAppDataStore.cs(135,20): error CS0029: Cannot implicitly convert type 'System.Threading.Tasks.Task>' to 'System.Threading.Tasks.ValueTask>' [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\Program.cs(34,56): error CS0234: The type or namespace name 'SoundFlowAudioManager' does not exist in the namespace 'BalatroSeedOracle.Services' (are you missing an assembly reference?) [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\Services\BrowserApiHostService.cs(17,32): warning CS0067: The event 'BrowserApiHostService.StatusChanged' is never used [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\Services\BrowserDuckDBAppender.cs(69,20): warning IL2026: Using member 'System.Text.Json.JsonSerializer.Serialize(TValue, JsonSerializerOptions)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved. [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\Services\BrowserDuckDBConnection.cs(52,20): warning IL2026: Using member 'System.Text.Json.JsonSerializer.Deserialize(String, JsonSerializerOptions)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved. [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\Services\BrowserDuckDBConnection.cs(34,20): warning IL2026: Using member 'System.Text.Json.JsonSerializer.Deserialize(String, JsonSerializerOptions)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved. [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] From 76ef1776657545a38782c78f57cacba484e669e5 Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 21:31:00 -0500 Subject: [PATCH 11/32] chore: drop publish_retry_2.txt (build log accidentally committed) --- publish_retry_2.txt | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 publish_retry_2.txt diff --git a/publish_retry_2.txt b/publish_retry_2.txt deleted file mode 100644 index 4e1b558..0000000 --- a/publish_retry_2.txt +++ /dev/null @@ -1,23 +0,0 @@ - Determining projects to restore... - All projects are up-to-date for restore. - Motely -> X:\BalatroSeedOracle\external\Motely\Motely\bin\Release\net10.0\Motely.dll - Motely.Orchestration -> X:\BalatroSeedOracle\external\Motely\Motely.Orchestration\bin\Release\net10.0\Motely.Orchestration.dll - BalatroSeedOracle -> X:\BalatroSeedOracle\src\BalatroSeedOracle\bin\Release\net10.0\BalatroSeedOracle.dll - Motely -> X:\BalatroSeedOracle\external\Motely\Motely\bin\Release\net10.0-browser\Motely.dll - Motely.Orchestration -> X:\BalatroSeedOracle\external\Motely\Motely.Orchestration\bin\Release\net10.0-browser\Motely.Orchestration.dll -X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\Services\BrowserLocalStorageAppDataStore.cs(55,20): error CS0029: Cannot implicitly convert type 'System.Threading.Tasks.Task' to 'System.Threading.Tasks.ValueTask' [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\Services\BrowserLocalStorageAppDataStore.cs(60,20): error CS0029: Cannot implicitly convert type 'System.Threading.Tasks.Task' to 'System.Threading.Tasks.ValueTask' [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\Services\BrowserLocalStorageAppDataStore.cs(98,20): error CS0029: Cannot implicitly convert type 'System.Threading.Tasks.Task' to 'System.Threading.Tasks.ValueTask' [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\Services\BrowserLocalStorageAppDataStore.cs(130,20): error CS0029: Cannot implicitly convert type 'System.Threading.Tasks.Task>' to 'System.Threading.Tasks.ValueTask>' [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\Services\BrowserLocalStorageAppDataStore.cs(135,20): error CS0029: Cannot implicitly convert type 'System.Threading.Tasks.Task>' to 'System.Threading.Tasks.ValueTask>' [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\Services\BrowserApiHostService.cs(17,32): warning CS0067: The event 'BrowserApiHostService.StatusChanged' is never used [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\Services\BrowserDuckDBAppender.cs(69,20): warning IL2026: Using member 'System.Text.Json.JsonSerializer.Serialize(TValue, JsonSerializerOptions)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved. [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\Services\BrowserDuckDBConnection.cs(34,20): warning IL2026: Using member 'System.Text.Json.JsonSerializer.Deserialize(String, JsonSerializerOptions)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved. [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\Services\BrowserDuckDBConnection.cs(52,20): warning IL2026: Using member 'System.Text.Json.JsonSerializer.Deserialize(String, JsonSerializerOptions)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved. [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\Program.cs(38,16): warning CA1416: This call site is reachable on all platforms. 'BrowserAppBuilder.StartBrowserAppAsync(AppBuilder, string, BrowserPlatformOptions?)' is only supported on: 'browser'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416) [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle\Services\SoundFlowAudioManager.cs(728,35): warning CA1416: This call site is reachable on all platforms. 'JSObject.GetPropertyAsDouble(string)' is only supported on: 'browser'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416) [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle\Services\SoundFlowAudioManager.cs(726,34): warning CA1416: This call site is reachable on all platforms. 'JSObject.GetPropertyAsDouble(string)' is only supported on: 'browser'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416) [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle\Services\SoundFlowAudioManager.cs(723,34): warning CA1416: This call site is reachable on all platforms. 'JSObject.GetPropertyAsDouble(string)' is only supported on: 'browser'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416) [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle\Services\SoundFlowAudioManager.cs(724,35): warning CA1416: This call site is reachable on all platforms. 'JSObject.GetPropertyAsDouble(string)' is only supported on: 'browser'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416) [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle\Services\SoundFlowAudioManager.cs(725,33): warning CA1416: This call site is reachable on all platforms. 'JSObject.GetPropertyAsDouble(string)' is only supported on: 'browser'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416) [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] -X:\BalatroSeedOracle\src\BalatroSeedOracle\Services\SoundFlowAudioManager.cs(727,34): warning CA1416: This call site is reachable on all platforms. 'JSObject.GetPropertyAsDouble(string)' is only supported on: 'browser'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416) [X:\BalatroSeedOracle\src\BalatroSeedOracle.Browser\BalatroSeedOracle.Browser.csproj] From efdbcc38c916d41d6de8cdedf9f90d728a1734a0 Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 21:31:01 -0500 Subject: [PATCH 12/32] chore: drop temp_duckdb_ops.txt (scratch notes) --- temp_duckdb_ops.txt | 95 --------------------------------------------- 1 file changed, 95 deletions(-) delete mode 100644 temp_duckdb_ops.txt diff --git a/temp_duckdb_ops.txt b/temp_duckdb_ops.txt deleted file mode 100644 index 1bcba8c..0000000 --- a/temp_duckdb_ops.txt +++ /dev/null @@ -1,95 +0,0 @@ -using DuckDB.NET.Data; - -namespace Motely.DuckDB; - -/// -/// Cross-platform DuckDB operations helper -/// -public static class DuckDBOperations -{ - /// - /// Get row count for a table - /// - public static long GetRowCount(DuckDBConnection connection, string tableName) - { - using var cmd = connection.CreateCommand(); - cmd.CommandText = $"SELECT COUNT(*) FROM {tableName}"; - var result = cmd.ExecuteScalar(); - return result != null ? Convert.ToInt64(result) : 0; - } - - /// - /// Check if a table exists - /// - public static bool TableExists(DuckDBConnection connection, string tableName) - { - try - { - using var cmd = connection.CreateCommand(); - cmd.CommandText = $"SELECT COUNT(*) FROM {tableName}"; - cmd.ExecuteScalar(); - return true; - } - catch - { - return false; - } - } - - /// - /// Check if a column exists in a table - /// - public static bool ColumnExists(DuckDBConnection connection, string tableName, string columnName) - { - try - { - using var cmd = connection.CreateCommand(); - cmd.CommandText = $@" - SELECT COUNT(*) - FROM information_schema.columns - WHERE table_name = '{tableName}' AND column_name = '{columnName}'"; - var result = cmd.ExecuteScalar(); - return result != null && Convert.ToInt64(result) > 0; - } - catch - { - return false; - } - } - - /// - /// Execute a query and return results - /// - public static List> ExecuteQuery(DuckDBConnection connection, string sql) - { - var results = new List>(); - using var cmd = connection.CreateCommand(); - cmd.CommandText = sql; - using var reader = cmd.ExecuteReader(); - - while (reader.Read()) - { - var row = new Dictionary(); - for (int i = 0; i < reader.FieldCount; i++) - { - row[reader.GetName(i)] = reader.IsDBNull(i) ? null : reader.GetValue(i); - } - results.Add(row); - } - - return results; - } - - /// - /// Execute a scalar query - /// - public static T? ExecuteScalar(DuckDBConnection connection, string sql) - { - using var cmd = connection.CreateCommand(); - cmd.CommandText = sql; - var result = cmd.ExecuteScalar(); - if (result == null || result == DBNull.Value) - return default; - return (T)Convert.ChangeType(result, typeof(T)); - } -} From c51aa490e3458255d1a72a9d75bf635cb8f864b0 Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 21:31:03 -0500 Subject: [PATCH 13/32] chore: drop src/BalatroSeedOracle/build_output.txt (build artifact in source tree) --- src/BalatroSeedOracle/build_output.txt | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 src/BalatroSeedOracle/build_output.txt diff --git a/src/BalatroSeedOracle/build_output.txt b/src/BalatroSeedOracle/build_output.txt deleted file mode 100644 index 2006ead..0000000 --- a/src/BalatroSeedOracle/build_output.txt +++ /dev/null @@ -1,23 +0,0 @@ - Determining projects to restore... - All projects are up-to-date for restore. - Motely -> x:\BalatroSeedOracle\external\Motely\Motely\bin\Release\net10.0\Motely.dll - Motely.Orchestration -> x:\BalatroSeedOracle\external\Motely\Motely.Orchestration\bin\Release\net10.0\Motely.Orchestration.dll -x:\BalatroSeedOracle\src\BalatroSeedOracle\Views\SearchModalTabs\ResultsTab.axaml.cs(231,56): error CS0234: The type or namespace name 'DataGridResultsWindow' does not exist in the namespace 'BalatroSeedOracle.Windows' (are you missing an assembly reference?) [x:\BalatroSeedOracle\src\BalatroSeedOracle\BalatroSeedOracle.csproj] -x:\BalatroSeedOracle\src\BalatroSeedOracle\Views\SearchModalTabs\ResultsTab.axaml.cs(256,18): error CS1061: 'SortableResultsGrid' does not contain a definition for 'ForceRefreshResults' and no accessible extension method 'ForceRefreshResults' accepting a first argument of type 'SortableResultsGrid' could be found (are you missing a using directive or an assembly reference?) [x:\BalatroSeedOracle\src\BalatroSeedOracle\BalatroSeedOracle.csproj] -x:\BalatroSeedOracle\src\BalatroSeedOracle\Views\BalatroMainMenu.axaml.cs(263,98): warning CS8600: Converting null literal or possible null value to non-nullable type. [x:\BalatroSeedOracle\src\BalatroSeedOracle\BalatroSeedOracle.csproj] -x:\BalatroSeedOracle\src\BalatroSeedOracle\Views\BalatroMainMenu.axaml.cs(270,46): warning CS8600: Converting null literal or possible null value to non-nullable type. [x:\BalatroSeedOracle\src\BalatroSeedOracle\BalatroSeedOracle.csproj] -x:\BalatroSeedOracle\src\BalatroSeedOracle\Services\SearchInstance.cs(885,55): warning CS8602: Dereference of a possibly null reference. [x:\BalatroSeedOracle\src\BalatroSeedOracle\BalatroSeedOracle.csproj] -x:\BalatroSeedOracle\src\BalatroSeedOracle\Helpers\JamlAutocompletionHelper.cs(19,38): warning CS0169: The field 'JamlAutocompletionHelper._schemaCache' is never used [x:\BalatroSeedOracle\src\BalatroSeedOracle\BalatroSeedOracle.csproj] - -Build FAILED. - -x:\BalatroSeedOracle\src\BalatroSeedOracle\Views\BalatroMainMenu.axaml.cs(263,98): warning CS8600: Converting null literal or possible null value to non-nullable type. [x:\BalatroSeedOracle\src\BalatroSeedOracle\BalatroSeedOracle.csproj] -x:\BalatroSeedOracle\src\BalatroSeedOracle\Views\BalatroMainMenu.axaml.cs(270,46): warning CS8600: Converting null literal or possible null value to non-nullable type. [x:\BalatroSeedOracle\src\BalatroSeedOracle\BalatroSeedOracle.csproj] -x:\BalatroSeedOracle\src\BalatroSeedOracle\Services\SearchInstance.cs(885,55): warning CS8602: Dereference of a possibly null reference. [x:\BalatroSeedOracle\src\BalatroSeedOracle\BalatroSeedOracle.csproj] -x:\BalatroSeedOracle\src\BalatroSeedOracle\Helpers\JamlAutocompletionHelper.cs(19,38): warning CS0169: The field 'JamlAutocompletionHelper._schemaCache' is never used [x:\BalatroSeedOracle\src\BalatroSeedOracle\BalatroSeedOracle.csproj] -x:\BalatroSeedOracle\src\BalatroSeedOracle\Views\SearchModalTabs\ResultsTab.axaml.cs(231,56): error CS0234: The type or namespace name 'DataGridResultsWindow' does not exist in the namespace 'BalatroSeedOracle.Windows' (are you missing an assembly reference?) [x:\BalatroSeedOracle\src\BalatroSeedOracle\BalatroSeedOracle.csproj] -x:\BalatroSeedOracle\src\BalatroSeedOracle\Views\SearchModalTabs\ResultsTab.axaml.cs(256,18): error CS1061: 'SortableResultsGrid' does not contain a definition for 'ForceRefreshResults' and no accessible extension method 'ForceRefreshResults' accepting a first argument of type 'SortableResultsGrid' could be found (are you missing a using directive or an assembly reference?) [x:\BalatroSeedOracle\src\BalatroSeedOracle\BalatroSeedOracle.csproj] - 4 Warning(s) - 2 Error(s) - -Time Elapsed 00:00:03.81 From d519d1199515f7e15dfef59d25f0810fa923e69f Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 21:31:04 -0500 Subject: [PATCH 14/32] chore: drop misnamed duplicate '# Browser Search Fix Plan.md' inside src/ Same content as root BROWSER_SEARCH_FIX_PLAN.md but with a hash-prefixed filename, sitting inside the source tree. Pure slop. --- .../# Browser Search Fix Plan.md | 104 ------------------ 1 file changed, 104 deletions(-) delete mode 100644 src/BalatroSeedOracle/# Browser Search Fix Plan.md diff --git a/src/BalatroSeedOracle/# Browser Search Fix Plan.md b/src/BalatroSeedOracle/# Browser Search Fix Plan.md deleted file mode 100644 index 731177f..0000000 --- a/src/BalatroSeedOracle/# Browser Search Fix Plan.md +++ /dev/null @@ -1,104 +0,0 @@ -# Browser Search Fix Plan - -## Documentation References - -**MUST READ before implementing:** - -- **Platform Abstraction Pattern**: https://docs.avaloniaui.net/docs/guides/building-cross-platform-applications/dealing-with-platforms -- **Dependency Injection**: https://docs.avaloniaui.net/docs/guides/implementation-guides/how-to-implement-dependency-injection -- **Project Architecture**: `x:\BalatroSeedOracle\docs\ARCHITECTURE.md` -- **Avalonia Best Practices**: `x:\BalatroSeedOracle\docs\AVALONIA_BEST_PRACTICES.md` - -## Key Rules from Docs - -1. **NO `#if BROWSER` or `#if !BROWSER`** in shared code - use interfaces + DI -2. **Interfaces** in Core/shared project (e.g., `IDuckDBService`, `IPlatformServices`) -3. **Platform implementations** in head projects (Desktop, Browser, iOS, Android) -4. **DI registration** via `PlatformServices.RegisterServices` at startup -5. **Runtime detection** via `IPlatformServices.SupportsFileSystem` (not compile-time) - -## Problem -`MotelySearchOrchestrator.Launch()` uses `MotelySearchDatabase` (native DuckDB) when `OutputDbPath` is provided. -This fails in Browser because DuckDB.NET doesn't support WASM - Browser needs JS interop to DuckDB-WASM. - -## Solution Architecture - -``` -Desktop: - SearchManager.StartSearchAsync() - → MotelySearchOrchestrator.Launch(config, params WITH OutputDbPath) - → Motely writes to MotelySearchDatabase (native DuckDB) - → ActiveSearchContext queries via IDuckDBService (DesktopDuckDBService) - -Browser: - SearchManager.StartSearchAsync() - → MotelySearchOrchestrator.Launch(config, params WITHOUT OutputDbPath, WITH resultCallback) - → Results come via callback - → SearchManager stores results via IDuckDBService (BrowserDuckDBService → DuckDB-WASM) - → ActiveSearchContext queries via IDuckDBService (BrowserDuckDBService) -``` - -## Files to Modify - -### 1. `src/BalatroSeedOracle/Services/SearchManager.cs` - -```csharp -// In StartSearchAsync(): - -var platformServices = ServiceHelper.GetService(); -var isBrowser = platformServices != null && !platformServices.SupportsFileSystem; - -if (isBrowser) -{ - // Browser: No OutputDbPath, use callback to store results - var duckDb = ServiceHelper.GetService(); - // Create in-memory DB via BrowserDuckDBService - // Use resultCallback to insert rows - - var searchParams = new JsonSearchParams - { - Threads = criteria.ThreadCount, - BatchSize = criteria.BatchSize, - // NO OutputDbPath! - }; - - var motelySearch = MotelySearchOrchestrator.Launch(config, searchParams, result => - { - // Store result via IDuckDBService - // This routes to BrowserDuckDBService → DuckDB-WASM - }); -} -else -{ - // Desktop: Use OutputDbPath (existing code) - var searchParams = new JsonSearchParams - { - OutputDbPath = dbPath, - // ... - }; - var motelySearch = MotelySearchOrchestrator.Launch(config, searchParams); -} -``` - -### 2. `src/BalatroSeedOracle/Services/ActiveSearchContext.cs` - -Already fixed - uses `IDuckDBService` for cross-platform DB queries. - -### 3. Browser DuckDB Table Creation - -`BrowserDuckDBService` needs to create the results table schema before search starts. -Use `MotelyRunConfig.Factory(config).Columns` to get column definitions. - -## Key Points - -1. **No `#if BROWSER`** - Use `IPlatformServices.SupportsFileSystem` at runtime -2. **One SearchManager** - Platform detection at runtime, not compile time -3. **IDuckDBService abstraction** - Already wired up correctly -4. **MotelySearchOrchestrator** - Works on all platforms, just don't pass OutputDbPath for browser - -## Testing - -1. Desktop: `dotnet build src/BalatroSeedOracle.Desktop` -2. Browser: `dotnet build src/BalatroSeedOracle.Browser` -3. Run Desktop and verify search works -4. Run Browser and verify search works (results stored in DuckDB-WASM) From 7feea0ffa554de78a459a0a30553b51a90af9252 Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 21:31:08 -0500 Subject: [PATCH 15/32] chore: drop stale TECH_DEBT_TODO.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Its top items (006-016: replace == null with is null in FiltersModalViewModel) reference patterns already fixed — verified by grep finding zero == null / != null in that file. List drifted away from reality and is misleading to future agents. --- TECH_DEBT_TODO.md | 309 ---------------------------------------------- 1 file changed, 309 deletions(-) delete mode 100644 TECH_DEBT_TODO.md diff --git a/TECH_DEBT_TODO.md b/TECH_DEBT_TODO.md deleted file mode 100644 index 5d13c1e..0000000 --- a/TECH_DEBT_TODO.md +++ /dev/null @@ -1,309 +0,0 @@ -# Tech Debt TODO - Small Fixes for AI Agents - -**Total Items**: 135+ -**Status**: Ready for AI agents to grab and complete -**Priority**: Small, quick fixes that improve code quality - -## ✅ Recently Completed (January 2026) - -### AOT Compilation Refactoring -- [x] **AOT_001**: Removed all reflection from `JamlTypeAsKeyConverter` - replaced with static property mappings -- [x] **AOT_002**: Implemented `System.Text.Json` source generation via `MotelyJsonSerializerContext` -- [x] **AOT_003**: Implemented YamlDotNet static context via `MotelyYamlStaticContext` -- [x] **AOT_004**: Enabled AOT for Desktop builds (`PublishAot=true`) -- [x] **AOT_005**: Enabled AOT for Browser builds (`RunAOTCompilation=true`, `PublishTrimmed=true`) -- [x] **AOT_006**: Created `IExcelExporter` interface for platform abstraction -- [x] **AOT_007**: Implemented `ClosedXmlExcelExporter` for Desktop -- [x] **AOT_008**: Implemented `BrowserExcelExporter` stub for Browser -- [x] **AOT_009**: Removed DuckDB references from shared `BalatroSeedOracle` library -- [x] **AOT_010**: Updated all `Enum.GetValues()` calls to use generic `Enum.GetValues()` - ---- - -## 🔴 Critical: Logging Violations - -- [ ] **TECH_DEBT_001**: Replace `Console.WriteLine(response)` in `MCP/McpServerHost.cs:54` with `DebugLogger.Log()` -- [ ] **TECH_DEBT_002**: Replace `Console.WriteLine(JsonSerializer.Serialize(errorResponse))` in `MCP/McpServerHost.cs:72` with `DebugLogger.LogError()` -- [ ] **TECH_DEBT_003**: Remove `Console.WriteLine` in `App.axaml.cs:55` - replace with `DebugLogger.Log()` -- [ ] **TECH_DEBT_004**: Remove `Debug.WriteLine` calls in `Services/Storage/BrowserLocalStorageAppDataStore.cs` (lines 19, 29, 47, 61) - use `DebugLogger` instead -- [ ] **TECH_DEBT_005**: Check `Helpers/DebugLogger.cs` for any `Console.WriteLine` that should be conditional on debug mode - ---- - -## 🟠 Code Style: Null Checks - -- [ ] **TECH_DEBT_006**: Replace `== null` with `is null` in `ViewModels/FiltersModalViewModel.cs:315` (`_originalCriteriaHash == null`) -- [ ] **TECH_DEBT_007**: Replace `== null` with `is null` in `ViewModels/FiltersModalViewModel.cs:399` (`searchManager != null`) -- [ ] **TECH_DEBT_008**: Replace `== null` with `is null` in `ViewModels/FiltersModalViewModel.cs:493` (`dbFiles == null`) -- [ ] **TECH_DEBT_009**: Replace `== null` with `is null` in `ViewModels/FiltersModalViewModel.cs:679` (`LoadedConfig != null`) -- [ ] **TECH_DEBT_010**: Replace `== null` with `is null` in `ViewModels/FiltersModalViewModel.cs:718` (`LoadedConfig != null`) -- [ ] **TECH_DEBT_011**: Replace `== null` with `is null` in `ViewModels/FiltersModalViewModel.cs:739` (`config.Must != null`) -- [ ] **TECH_DEBT_012**: Replace `== null` with `is null` in `ViewModels/FiltersModalViewModel.cs:753` (`config.Should != null`) -- [ ] **TECH_DEBT_013**: Replace `== null` with `is null` in `ViewModels/FiltersModalViewModel.cs:767` (`config.MustNot != null`) -- [ ] **TECH_DEBT_014**: Replace `== null` with `is null` in `ViewModels/FiltersModalViewModel.cs:805` (`config != null`) -- [ ] **TECH_DEBT_015**: Replace `== null` with `is null` in `ViewModels/FiltersModalViewModel.cs:1013` (`config == null`) -- [ ] **TECH_DEBT_016**: Replace all `== null` / `!= null` checks in `ViewModels/FiltersModalViewModel.cs` lines 1123-1801 (scan entire file) -- [ ] **TECH_DEBT_017**: Scan all ViewModels for `== null` / `!= null` and replace with `is null` / `is not null` - ---- - -## 🟡 Async/Await Issues - -- [ ] **TECH_DEBT_018**: Check `ViewModels/FiltersModalViewModel.cs:289` `SaveCurrentFilter()` - verify it properly awaits all async calls -- [ ] **TECH_DEBT_019**: Check `ViewModels/FiltersModalViewModel.cs:381` `CleanupFilterDatabases()` - verify async pattern -- [ ] **TECH_DEBT_020**: Check `ViewModels/FiltersModalViewModel.cs:486` `DumpDatabasesToFertilizerAsync()` - verify async pattern -- [ ] **TECH_DEBT_021**: Check `ViewModels/FiltersModalViewModel.cs:573` `LoadFilter()` - verify async pattern -- [ ] **TECH_DEBT_022**: Check `ViewModels/FiltersModalViewModel.cs:687` `DeleteFilter()` - verify async pattern -- [ ] **TECH_DEBT_023**: Check `ViewModels/FiltersModalViewModel.cs:783` `ReloadVisualFromSavedFile()` - verify async pattern -- [ ] **TECH_DEBT_024**: Check `ViewModels/FiltersModalViewModel.cs:892` `RefreshSaveTabData()` - verify async pattern -- [ ] **TECH_DEBT_025**: Check `ViewModels/FiltersModalViewModel.cs:2108` `UpdateVisualBuilderFromItemConfigs()` - verify async pattern -- [ ] **TECH_DEBT_026**: Check `ViewModels/AnalyzerViewModel.cs:244` `AnalyzeCurrentSeedAsync()` - verify async pattern -- [ ] **TECH_DEBT_027**: Check all async methods in `ViewModels/FilterTabs/ValidateFilterTabViewModel.cs` - verify they properly await -- [ ] **TECH_DEBT_028**: Check all async methods in `ViewModels/FilterTabs/SaveFilterTabViewModel.cs` - verify they properly await - ---- - -## 🟢 MVVM Violations: Code-Behind Logic - -- [ ] **TECH_DEBT_029**: Move business logic from `Views/MainWindow.axaml.cs:57-64` `OnWindowClosing()` to `MainWindowViewModel` -- [ ] **TECH_DEBT_030**: Move business logic from `Views/MainWindow.axaml.cs:54` `OnWindowSizeChanged()` to `MainWindowViewModel` -- [ ] **TECH_DEBT_031**: Review `Views/BalatroMainMenu.axaml.cs` for any business logic that should be in ViewModel -- [ ] **TECH_DEBT_032**: Review all `.axaml.cs` files in `Views/` for business logic that belongs in ViewModels -- [ ] **TECH_DEBT_033**: Review all `.axaml.cs` files in `Components/` for business logic that belongs in ViewModels -- [ ] **TECH_DEBT_034**: Check `Components/FilterTabs/JamlEditorTab.axaml.cs` - ensure all logic is in `JamlEditorTabViewModel` -- [ ] **TECH_DEBT_035**: Check `Controls/SortableResultsGrid.axaml.cs` - ensure all logic is in `SortableResultsGridViewModel` - ---- - -## 🔵 Bad Comments / AI Slop - -- [ ] **TECH_DEBT_036**: Remove or improve generic XML comments like `/// ` with no actual description -- [ ] **TECH_DEBT_037**: Remove redundant comments that just repeat the code (e.g., `// Set the value` above `value = 5`) -- [ ] **TECH_DEBT_038**: Remove "AI-generated" style comments that are obvious (e.g., `// Initialize the variable`) -- [ ] **TECH_DEBT_039**: Review `ViewModels/FiltersModalViewModel.cs` for bad XML comments - improve or remove -- [ ] **TECH_DEBT_040**: Review `ViewModels/FilterTabs/SaveFilterTabViewModel.cs` for bad XML comments - improve or remove -- [ ] **TECH_DEBT_041**: Remove comments like `// BUG FIX:` - if it's fixed, the comment is unnecessary -- [ ] **TECH_DEBT_042**: Remove comments like `// TODO:` that are outdated or already implemented -- [ ] **TECH_DEBT_043**: Clean up comments in `Services/Storage/BrowserLocalStorageAppDataStore.cs` - remove obvious ones - ---- - -## 🟣 Error Handling - -- [ ] **TECH_DEBT_044**: Add proper error handling to `ViewModels/FiltersModalViewModel.cs:289` `SaveCurrentFilter()` if missing -- [ ] **TECH_DEBT_045**: Add proper error handling to `ViewModels/FiltersModalViewModel.cs:381` `CleanupFilterDatabases()` if missing -- [ ] **TECH_DEBT_046**: Check all async methods for missing try-catch blocks -- [ ] **TECH_DEBT_047**: Ensure all file I/O operations have proper error handling -- [ ] **TECH_DEBT_048**: Ensure all service calls have proper error handling -- [ ] **TECH_DEBT_049**: Check for empty catch blocks that should log errors - ---- - -## 🟤 Naming Conventions - -- [ ] **TECH_DEBT_050**: Check for methods that don't follow naming conventions (e.g., `DoSomething()` should be `DoSomethingAsync()` if async) -- [ ] **TECH_DEBT_051**: Check for private fields that don't use `_camelCase` convention -- [ ] **TECH_DEBT_052**: Check for properties that don't use `PascalCase` convention -- [ ] **TECH_DEBT_053**: Check for local variables that use inconsistent naming - ---- - -## ⚪ Code Duplication - -- [ ] **TECH_DEBT_054**: Find duplicate error handling patterns and extract to helper method -- [ ] **TECH_DEBT_055**: Find duplicate null check patterns and extract to helper method -- [ ] **TECH_DEBT_056**: Find duplicate logging patterns and standardize -- [ ] **TECH_DEBT_057**: Check `ViewModels/FiltersModalViewModel.cs` for duplicate code blocks -- [ ] **TECH_DEBT_058**: Check `ViewModels/FilterTabs/` for duplicate code blocks - ---- - -## 🔴 Unused Code - -- [ ] **TECH_DEBT_059**: Remove unused `using` statements across all files -- [ ] **TECH_DEBT_060**: Remove unused private fields -- [ ] **TECH_DEBT_061**: Remove unused private methods -- [ ] **TECH_DEBT_062**: Remove commented-out code blocks -- [ ] **TECH_DEBT_063**: Remove dead code paths - ---- - -## 🟠 Magic Numbers / Strings - -- [ ] **TECH_DEBT_064**: Replace magic numbers with named constants (e.g., `500` → `VALIDATION_DELAY_MS`) -- [ ] **TECH_DEBT_065**: Replace magic strings with constants (e.g., `"Filters"` → `MODAL_TYPE_FILTERS`) -- [ ] **TECH_DEBT_066**: Check `ViewModels/FiltersModalViewModel.cs` for magic numbers/strings -- [ ] **TECH_DEBT_067**: Check `ViewModels/FilterTabs/` for magic numbers/strings - ---- - -## 🟡 Inconsistent Patterns - -- [ ] **TECH_DEBT_068**: Standardize error logging format across all ViewModels -- [ ] **TECH_DEBT_069**: Standardize success logging format across all ViewModels -- [ ] **TECH_DEBT_070**: Standardize async method patterns (all should use `ConfigureAwait(false)` for library code) -- [ ] **TECH_DEBT_071**: Standardize command patterns (all should use `[RelayCommand]` consistently) -- [ ] **TECH_DEBT_072**: Standardize property change notification patterns - ---- - -## 🟢 Missing Documentation - -- [ ] **TECH_DEBT_073**: Add XML comments to public methods in `Services/FilterService.cs` -- [ ] **TECH_DEBT_074**: Add XML comments to public methods in `Services/SearchInstance.cs` -- [ ] **TECH_DEBT_075**: Add XML comments to public properties in ViewModels -- [ ] **TECH_DEBT_076**: Add XML comments to complex business logic methods - ---- - -## 🔵 Performance Issues - -- [ ] **TECH_DEBT_077**: Check for LINQ queries that could be optimized (e.g., `.ToList()` when not needed) -- [ ] **TECH_DEBT_078**: Check for string concatenation in loops (use `StringBuilder`) -- [ ] **TECH_DEBT_079**: Check for unnecessary object allocations in hot paths -- [ ] **TECH_DEBT_080**: Check for missing `ConfigureAwait(false)` in library code - ---- - -## 🟣 Type Safety - -- [ ] **TECH_DEBT_081**: Replace `var` with explicit types where it improves readability -- [ ] **TECH_DEBT_082**: Check for unnecessary type casts -- [ ] **TECH_DEBT_083**: Check for `as` casts that should use pattern matching -- [ ] **TECH_DEBT_084**: Check for nullable reference type warnings - ---- - -## 🟤 Platform-Specific Code - -- [ ] **TECH_DEBT_085**: Ensure all `#if BROWSER` blocks have corresponding `#else` blocks -- [ ] **TECH_DEBT_086**: Check for platform-specific code that's not properly guarded -- [ ] **TECH_DEBT_087**: Verify browser-specific code doesn't break desktop builds - ---- - -## ⚪ Resource Management - -- [ ] **TECH_DEBT_088**: Check for missing `IDisposable` implementations -- [ ] **TECH_DEBT_089**: Check for missing `using` statements for `IDisposable` objects -- [ ] **TECH_DEBT_090**: Check for event handlers that aren't unsubscribed -- [ ] **TECH_DEBT_091**: Check for memory leaks in long-running operations - ---- - -## 🔴 Validation - -- [ ] **TECH_DEBT_092**: Add input validation to all public methods -- [ ] **TECH_DEBT_093**: Add null checks for all constructor parameters -- [ ] **TECH_DEBT_094**: Add validation for file paths before I/O operations -- [ ] **TECH_DEBT_095**: Add validation for user input in ViewModels - ---- - -## 🟠 Testing - -- [ ] **TECH_DEBT_096**: Add unit tests for `Helpers/DebugLogger.cs` -- [ ] **TECH_DEBT_097**: Add unit tests for `Services/FilterService.cs` -- [ ] **TECH_DEBT_098**: Add unit tests for ViewModel commands -- [ ] **TECH_DEBT_099**: Add integration tests for filter loading/saving - ---- - -## 🟡 Miscellaneous - -- [ ] **TECH_DEBT_100**: Remove all `// TODO:` comments that are outdated -- [ ] **TECH_DEBT_101**: Remove all `// FIXME:` comments that are fixed -- [ ] **TECH_DEBT_102**: Remove all `// HACK:` comments - either fix or document why it's needed -- [ ] **TECH_DEBT_103**: Standardize file headers (copyright, license, etc.) -- [ ] **TECH_DEBT_104**: Ensure all files have consistent line endings (LF) -- [ ] **TECH_DEBT_105**: Remove trailing whitespace from all files -- [ ] **TECH_DEBT_106**: Ensure consistent indentation (spaces, not tabs) -- [ ] **TECH_DEBT_107**: Remove unused project references -- [ ] **TECH_DEBT_108**: Update outdated package versions -- [ ] **TECH_DEBT_109**: Remove duplicate resource definitions -- [ ] **TECH_DEBT_110**: Ensure all XAML files use consistent naming conventions - ---- - -## 🟢 Code Quality - -- [ ] **TECH_DEBT_111**: Reduce cyclomatic complexity in methods over 20 lines -- [ ] **TECH_DEBT_112**: Break down methods over 50 lines into smaller methods -- [ ] **TECH_DEBT_113**: Extract magic numbers to constants -- [ ] **TECH_DEBT_114**: Extract repeated patterns to helper methods -- [ ] **TECH_DEBT_115**: Improve variable names for clarity - ---- - -## 🔵 Accessibility - -- [ ] **TECH_DEBT_116**: Add proper ARIA labels to interactive elements -- [ ] **TECH_DEBT_117**: Ensure keyboard navigation works for all controls -- [ ] **TECH_DEBT_118**: Add tooltips to icon-only buttons -- [ ] **TECH_DEBT_119**: Ensure color contrast meets WCAG standards - ---- - -## 🟣 Localization - -- [ ] **TECH_DEBT_120**: Extract hardcoded strings to resource files -- [ ] **TECH_DEBT_121**: Add localization support for error messages -- [ ] **TECH_DEBT_122**: Add localization support for UI text - ---- - -## 🟤 Documentation - -- [ ] **TECH_DEBT_123**: Update README.md with latest features -- [ ] **TECH_DEBT_124**: Add code examples to complex methods -- [ ] **TECH_DEBT_125**: Document complex algorithms -- [ ] **TECH_DEBT_126**: Add architecture diagrams - ---- - -## ⚪ Security - -- [ ] **TECH_DEBT_127**: Review file path handling for path traversal vulnerabilities -- [ ] **TECH_DEBT_128**: Review user input validation for injection attacks -- [ ] **TECH_DEBT_129**: Ensure sensitive data isn't logged -- [ ] **TECH_DEBT_130**: Review authentication/authorization if applicable - ---- - -## 🔴 Final Polish - -- [ ] **TECH_DEBT_131**: Run code formatter on entire solution -- [ ] **TECH_DEBT_132**: Fix all compiler warnings -- [ ] **TECH_DEBT_133**: Fix all linter errors -- [ ] **TECH_DEBT_134**: Run static analysis tools -- [ ] **TECH_DEBT_135**: Review code coverage report - ---- - -## 📝 How to Use This List - -1. **Pick an item** - Any AI agent can grab any item -2. **Complete it** - Make the fix -3. **Mark it done** - Check the box `[x]` -4. **Commit** - Small, focused commits are best -5. **Move on** - Grab the next item - -### Priority Order (Suggested) -1. Critical logging violations (001-005) -2. Code style null checks (006-017) -3. Async/await issues (018-028) -4. MVVM violations (029-035) -5. Bad comments (036-043) -6. Then work through the rest - -### Notes -- Each item is **small and actionable** -- Most items take **5-15 minutes** to complete -- Items are **independent** - can be done in any order -- **Test after each fix** - don't break existing functionality - ---- - -**Last Updated**: 2025-01-XX -**Total Items**: 135+ -**Status**: Ready for AI agents 🚀 From db9db0d86969b7664928bc2005589a4c29c56f73 Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 21:31:10 -0500 Subject: [PATCH 16/32] chore: drop HOTPATH_AUDIT_FINDINGS.md (completion report, not active doc) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit File reads as a "✅ All Fixes Applied" / "Build Status: ✅" past-tense report. Belongs in PR description, not in the repo root. --- HOTPATH_AUDIT_FINDINGS.md | 54 --------------------------------------- 1 file changed, 54 deletions(-) delete mode 100644 HOTPATH_AUDIT_FINDINGS.md diff --git a/HOTPATH_AUDIT_FINDINGS.md b/HOTPATH_AUDIT_FINDINGS.md deleted file mode 100644 index 5d78b5f..0000000 --- a/HOTPATH_AUDIT_FINDINGS.md +++ /dev/null @@ -1,54 +0,0 @@ -# MotelyJson Filter SIMD Hotpath Audit - COMPLETED - -## ✅ All Fixes Applied - -### 1. MotelyJsonVoucherFilterDesc ✅ -- Removed debug logging from CreateFilter -- Removed bounds checking from hotpath (trust config) - -### 2. MotelyJsonScoring.CheckSoulJokerForSeed ✅ -- Removed recalculation of `minAnte`, `maxAnte` - now parameters -- Removed recalculation of `maxPackSlots` - use precomputed `maxPackSlotsPerAnte` -- Removed unnecessary ante-needed check (loop starts at precomputed `minAnte`) -- All call sites updated to pass precomputed values - -### 3. MotelyJsonPlayingCardFilterDesc ✅ -- Removed null check on `EffectiveAntes` in CreateFilter -- Removed null coalescing (`?? Array.Empty()`) in hotpath - -### 4. MotelyJsonTagFilterDesc ✅ -- Optimized ante checking logic (removed redundant array search) -- Removed null check on `EffectiveAntes` in early exit loop - -### 5. All Other Filters ✅ -- Joker, Tarot, Spectral, Planet, Event, Boss: All clean - no unnecessary hotpath waste found -- Any remaining null checks are on optional config fields (Sources, Tags, etc.) which is correct - ---- - -## Philosophy Locked In - -**Trust config at parse time. Hotpaths are pure logic.** - -- ✅ Zero defensive checks on precomputed fields -- ✅ Zero recalculations of precomputed values -- ✅ Zero null coalescing in SIMD paths -- ✅ Parameters pass all necessary precomputed values - ---- - -## Build Status - -✅ **All files compile successfully** - Ready for deployment - ---- - -## Files Changed - -1. `MotelyJsonVoucherFilterDesc.cs` - Removed asserts and debug logging -2. `MotelyJsonScoring.cs` - Removed hotpath recalculations, updated signature -3. `MotelyJsonSoulJokerFilterDesc.cs` - Updated call site -4. `MotelyJsonSeedScoreDesc.cs` - Updated call sites (2 locations) -5. `MotelyJsonPlayingCardFilterDesc.cs` - Removed null checks -6. `MotelyJsonTagFilterDesc.cs` - Removed null checks in hotpath - From 6498dde86b264f932abdaf3e5783f7ac9a61da94 Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 21:31:42 -0500 Subject: [PATCH 17/32] chore: drop MONO_WASM_FIX.md (post-mortem for completed WASM fix) --- MONO_WASM_FIX.md | 101 ----------------------------------------------- 1 file changed, 101 deletions(-) delete mode 100644 MONO_WASM_FIX.md diff --git a/MONO_WASM_FIX.md b/MONO_WASM_FIX.md deleted file mode 100644 index 0478f38..0000000 --- a/MONO_WASM_FIX.md +++ /dev/null @@ -1,101 +0,0 @@ -# Mono WASM Assertion Failure Fix - -## Problem - -**Symptom**: Palindrome search (and potentially other searches) in the browser cause a Mono WASM runtime assertion failure: - -``` -[MONO] * Assertion at /__w/1/s/src/runtime/src/mono/mono/mini/mini-runtime.c:2713, condition '' not met -``` - -**Root Cause**: Mono WASM has known issues with `Barrier` synchronization primitives used in multi-threaded mode. The assertion at line 2713 in the Mono runtime's mini-runtime indicates that a disabled or unsupported threading feature was attempted. - -**Affected Code Path**: -1. User initiates palindrome search in browser -2. `MotelyWasm.SearchSeedsWithOptions()` creates a `SearchOptionsDto` with `Palindrome = true` -3. `MotelyPalindromeSeedProvider` is created to lazily generate palindrome seeds -4. `MotelySearch` spawns multiple threads (default: `Environment.ProcessorCount`) -5. Threads use `Barrier` for synchronization in the pause/unpause flow -6. **Mono WASM assertion fails** during barrier synchronization - -## Solution - -**Approach**: Force single-threaded search mode for WASM/browser builds by capping `threadCount` to 1. - -**Rationale**: -- Mono WASM threading support is incomplete, especially for advanced synchronization primitives -- Single-threaded mode eliminates all `Barrier` usage, avoiding the runtime assertion -- Performance impact is minimal in browser context (limited CPU anyway) -- All search functionality still works (just sequential instead of parallel) - -**Changes Made**: - -### File: `x:\BalatroSeedOracle\external\Motely\Motely.WASM\MotelyWasm.cs` (lines 276-279) - -**Before**: -```csharp -int threadCount = options.ThreadCount.GetValueOrDefault(); -threadCount = threadCount <= 0 ? Environment.ProcessorCount : Math.Clamp(threadCount, 1, Environment.ProcessorCount); -_searchThreadCount = threadCount; -``` - -**After**: -```csharp -int threadCount = options.ThreadCount.GetValueOrDefault(); -// WASM has issues with Barrier synchronization in multi-threaded mode (Mono WASM assertion failure) -// Force single-threaded search for WASM builds to avoid runtime assertion at mini-runtime.c:2713 -threadCount = 1; -_searchThreadCount = threadCount; -``` - -## Test Cases - -### Palindrome Search (Previously Failing) -- **Before Fix**: Crashes with Mono WASM assertion at mini-runtime.c:2713 -- **After Fix**: Completes successfully with single-threaded execution - -### Random Search -- Should work (was likely working before, now guaranteed) - -### Keyword Search -- Should work with single-threaded fallback - -### Sequential/JAML Filter Search -- Should work with single-threaded fallback - -## Trade-offs - -| Aspect | Impact | -|--------|--------| -| Correctness | ✅ Fixed - no more assertion errors | -| Performance | ⚠️ Slower (single-threaded) but still functional | -| Compatibility | ✅ Maintained - only affects browser builds | -| User Experience | ✅ Better - searches complete without crashing | - -## Alternative Approaches Considered - -1. **Disable Barriers entirely**: Requires deeper changes to `MotelySearch` architecture -2. **Use semaphores instead of Barriers**: Still likely to fail in Mono WASM -3. **WebWorker-based threading**: Would require complete architectural rewrite -4. **Upgrade Mono/.NET version**: Future option when WASM threading is more mature - -## Future Improvements - -When Mono WASM threading is more mature (or we migrate to newer .NET versions with better WASM support), we can: - -1. Re-enable multi-threaded search -2. Profile to find optimal thread count for browser -3. Use WebWorkers for true parallel execution (different from managed threads) -4. Add user-facing thread count control in UI - -## Version Impact - -- **motely-wasm v1.0.4**: Multi-threaded (broken for palindrome search) -- **motely-wasm v1.0.5**: Single-threaded (fixed, palindrome search works) -- **BalatroSeedOracle.Browser**: Should update to use motely-wasm v1.0.5+ - -## References - -- Mono WASM Threading Limitations: https://github.com/dotnet/runtime/issues -- Balatro Seed Oracle Issue: Search doesn't find seeds (Mono WASM assertion) -- Test Environment: Vercel v0 preview (browser-based WASM execution) From a86946f1c0db869a4c5c7b64c499e8365848605f Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 21:31:44 -0500 Subject: [PATCH 18/32] chore: drop BROWSER_COOP_COEP_FIX.md (post-mortem for completed COOP/COEP fix) --- BROWSER_COOP_COEP_FIX.md | 79 ---------------------------------------- 1 file changed, 79 deletions(-) delete mode 100644 BROWSER_COOP_COEP_FIX.md diff --git a/BROWSER_COOP_COEP_FIX.md b/BROWSER_COOP_COEP_FIX.md deleted file mode 100644 index d900a79..0000000 --- a/BROWSER_COOP_COEP_FIX.md +++ /dev/null @@ -1,79 +0,0 @@ -# Browser COOP/COEP Headers Fix - -## The Problem - -Browsers require `Cross-Origin-Opener-Policy` (COOP) and `Cross-Origin-Embedder-Policy` (COEP) headers to enable `SharedArrayBuffer`, which is required for .NET WASM threading. - -**However**, browsers only trust these headers from "secure contexts": -- ✅ `localhost` (trusted) -- ✅ `127.0.0.1` (usually trusted) -- ✅ HTTPS URLs -- ❌ **IP addresses over HTTP are NOT trusted** - -## Why You Keep Seeing This Error - -When you access `http://192.168.0.171:3141/BSO/`, the browser sees an "untrustworthy origin" and ignores the COOP/COEP headers, even though the server is sending them correctly. - -## Solutions - -### ✅ Solution 1: Use `localhost` (Recommended for Local Development) - -**Change your URL from:** -``` -http://192.168.0.171:3141/BSO/ -``` - -**To:** -``` -http://localhost:3141/BSO/ -``` - -This works immediately - no configuration changes needed. - -### ✅ Solution 2: Use `127.0.0.1` (Alternative) - -``` -http://127.0.0.1:3141/BSO/ -``` - -### ⚠️ Solution 3: Set Up HTTPS (Required for Network Access) - -If you need to access from another device on your network, you MUST use HTTPS: - -1. Generate a self-signed certificate: -```powershell -# Run as Administrator -New-SelfSignedCertificate -DnsName "localhost", "192.168.0.171" -CertStoreLocation "cert:\LocalMachine\My" -FriendlyName "BSO Dev Cert" -``` - -2. Configure Kestrel in `MotelyApiHost.cs` to use HTTPS - -3. Access via: `https://192.168.0.171:3141/BSO/` - -### ❌ Solution 4: Browser Flags (NOT Recommended) - -You can start Chrome with flags to allow insecure origins: -```powershell -chrome.exe --unsafely-treat-insecure-origin-as-secure=http://192.168.0.171:3141 -``` - -**Warning:** This is insecure and should only be used for development. - -## ✅ Permanent Fix Applied - -**Update:** `ApiServerWindow.cs` now automatically listens on **both** `localhost` and the configured IP address. This means: - -- ✅ Browser can use `http://localhost:3141/BSO/` (COOP/COEP headers work) -- ✅ Network access still works via `http://192.168.0.171:3141/BSO/` (for other devices) - -**After restarting Motely.TUI**, the API will be available on both URLs. Just use `localhost` in your browser! - -## Quick Fix Right Now - -**Just use `localhost` instead of the IP address:** - -``` -http://localhost:3141/BSO/ -``` - -The headers are already configured correctly - the browser just needs a trusted origin to accept them. From a73149d636393b22d594083ffeb57564e16839c2 Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 21:31:45 -0500 Subject: [PATCH 19/32] chore: drop BROWSER_SEARCH_FIX_PLAN.md (fix-plan doc that has rotted) --- BROWSER_SEARCH_FIX_PLAN.md | 104 ------------------------------------- 1 file changed, 104 deletions(-) delete mode 100644 BROWSER_SEARCH_FIX_PLAN.md diff --git a/BROWSER_SEARCH_FIX_PLAN.md b/BROWSER_SEARCH_FIX_PLAN.md deleted file mode 100644 index 731177f..0000000 --- a/BROWSER_SEARCH_FIX_PLAN.md +++ /dev/null @@ -1,104 +0,0 @@ -# Browser Search Fix Plan - -## Documentation References - -**MUST READ before implementing:** - -- **Platform Abstraction Pattern**: https://docs.avaloniaui.net/docs/guides/building-cross-platform-applications/dealing-with-platforms -- **Dependency Injection**: https://docs.avaloniaui.net/docs/guides/implementation-guides/how-to-implement-dependency-injection -- **Project Architecture**: `x:\BalatroSeedOracle\docs\ARCHITECTURE.md` -- **Avalonia Best Practices**: `x:\BalatroSeedOracle\docs\AVALONIA_BEST_PRACTICES.md` - -## Key Rules from Docs - -1. **NO `#if BROWSER` or `#if !BROWSER`** in shared code - use interfaces + DI -2. **Interfaces** in Core/shared project (e.g., `IDuckDBService`, `IPlatformServices`) -3. **Platform implementations** in head projects (Desktop, Browser, iOS, Android) -4. **DI registration** via `PlatformServices.RegisterServices` at startup -5. **Runtime detection** via `IPlatformServices.SupportsFileSystem` (not compile-time) - -## Problem -`MotelySearchOrchestrator.Launch()` uses `MotelySearchDatabase` (native DuckDB) when `OutputDbPath` is provided. -This fails in Browser because DuckDB.NET doesn't support WASM - Browser needs JS interop to DuckDB-WASM. - -## Solution Architecture - -``` -Desktop: - SearchManager.StartSearchAsync() - → MotelySearchOrchestrator.Launch(config, params WITH OutputDbPath) - → Motely writes to MotelySearchDatabase (native DuckDB) - → ActiveSearchContext queries via IDuckDBService (DesktopDuckDBService) - -Browser: - SearchManager.StartSearchAsync() - → MotelySearchOrchestrator.Launch(config, params WITHOUT OutputDbPath, WITH resultCallback) - → Results come via callback - → SearchManager stores results via IDuckDBService (BrowserDuckDBService → DuckDB-WASM) - → ActiveSearchContext queries via IDuckDBService (BrowserDuckDBService) -``` - -## Files to Modify - -### 1. `src/BalatroSeedOracle/Services/SearchManager.cs` - -```csharp -// In StartSearchAsync(): - -var platformServices = ServiceHelper.GetService(); -var isBrowser = platformServices != null && !platformServices.SupportsFileSystem; - -if (isBrowser) -{ - // Browser: No OutputDbPath, use callback to store results - var duckDb = ServiceHelper.GetService(); - // Create in-memory DB via BrowserDuckDBService - // Use resultCallback to insert rows - - var searchParams = new JsonSearchParams - { - Threads = criteria.ThreadCount, - BatchSize = criteria.BatchSize, - // NO OutputDbPath! - }; - - var motelySearch = MotelySearchOrchestrator.Launch(config, searchParams, result => - { - // Store result via IDuckDBService - // This routes to BrowserDuckDBService → DuckDB-WASM - }); -} -else -{ - // Desktop: Use OutputDbPath (existing code) - var searchParams = new JsonSearchParams - { - OutputDbPath = dbPath, - // ... - }; - var motelySearch = MotelySearchOrchestrator.Launch(config, searchParams); -} -``` - -### 2. `src/BalatroSeedOracle/Services/ActiveSearchContext.cs` - -Already fixed - uses `IDuckDBService` for cross-platform DB queries. - -### 3. Browser DuckDB Table Creation - -`BrowserDuckDBService` needs to create the results table schema before search starts. -Use `MotelyRunConfig.Factory(config).Columns` to get column definitions. - -## Key Points - -1. **No `#if BROWSER`** - Use `IPlatformServices.SupportsFileSystem` at runtime -2. **One SearchManager** - Platform detection at runtime, not compile time -3. **IDuckDBService abstraction** - Already wired up correctly -4. **MotelySearchOrchestrator** - Works on all platforms, just don't pass OutputDbPath for browser - -## Testing - -1. Desktop: `dotnet build src/BalatroSeedOracle.Desktop` -2. Browser: `dotnet build src/BalatroSeedOracle.Browser` -3. Run Desktop and verify search works -4. Run Browser and verify search works (results stored in DuckDB-WASM) From 16266c497763b5edd84bfb2e6e8a828744d42b84 Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 21:31:47 -0500 Subject: [PATCH 20/32] chore: drop JAML_EDITOR_ENHANCEMENTS.md (enhancement plan from past iteration) --- JAML_EDITOR_ENHANCEMENTS.md | 577 ------------------------------------ 1 file changed, 577 deletions(-) delete mode 100644 JAML_EDITOR_ENHANCEMENTS.md diff --git a/JAML_EDITOR_ENHANCEMENTS.md b/JAML_EDITOR_ENHANCEMENTS.md deleted file mode 100644 index aad7323..0000000 --- a/JAML_EDITOR_ENHANCEMENTS.md +++ /dev/null @@ -1,577 +0,0 @@ -# JAML Editor Enhancement Plan -## Making It REAL...SOLID...GREAT...COZY 🎯 - -Based on research into modern code editor best practices and YAML editor features, here's a comprehensive enhancement plan to make the JAML editor world-class. - ---- - -## 🎨 Visual Enhancements - -### 1. **Error Squiggles & Underlines** ⚠️ -**Priority: HIGH** - -- Red squiggly underlines for syntax errors -- Yellow warnings for schema violations -- Blue info for suggestions -- Click error → jump to problem - -**Implementation:** -```csharp -// Use AvaloniaEdit's TextMarkerService -var markerService = new TextMarkerService(_jamlEditor.Document); -_jamlEditor.TextArea.TextView.BackgroundRenderers.Add(markerService); - -// Mark errors -var marker = markerService.Create(startOffset, length); -marker.MarkerColor = Colors.Red; -marker.MarkerTypes = TextMarkerTypes.SquigglyUnderline; -``` - -**Benefits:** -- Immediate visual feedback -- No need to check status bar -- Professional editor feel - ---- - -### 2. **Hover Tooltips** 💡 -**Priority: HIGH** - -- Hover over property → show description from schema -- Hover over joker → show joker info (rarity, description) -- Hover over anchor → show anchor definition -- Hover over error → show error details - -**Implementation:** -```csharp -_jamlEditor.TextArea.TextView.MouseHover += (s, e) => { - var position = _jamlEditor.GetPositionFromPoint(e.GetPosition(_jamlEditor)); - if (position.HasValue) { - var word = GetWordAtPosition(position.Value); - var tooltip = GetTooltipForWord(word); - ShowTooltip(tooltip, e.GetPosition(_jamlEditor)); - } -}; -``` - -**Tooltip Content:** -- **Property**: Description from `jaml.schema.json` -- **Joker**: Name, rarity, description from `BalatroData` -- **Anchor**: Full definition preview -- **Error**: Error message + fix suggestion - ---- - -### 3. **Current Line Highlighting** ✨ -**Priority: MEDIUM** - -- Subtle background highlight on current line -- Makes it easy to see where you are - -**Implementation:** -```csharp -_jamlEditor.Options.HighlightCurrentLine = true; -_jamlEditor.TextArea.IndentationSize = 2; -``` - ---- - -### 4. **Bracket Matching** 🔗 -**Priority: MEDIUM** - -- Highlight matching brackets `[]`, `{}` -- Highlight when cursor is on bracket -- Visual indicator for nested structures - -**Implementation:** -```csharp -_jamlEditor.TextArea.TextView.BracketMatchingBrush = Brushes.Yellow; -_jamlEditor.Options.EnableBracketMatching = true; -``` - ---- - -### 5. **Minimap** 🗺️ -**Priority: LOW** - -- Small overview of entire file on right side -- Click to jump to section -- Shows structure at a glance - -**Note**: AvaloniaEdit may need custom implementation or third-party control. - ---- - -## 🧭 Navigation Features - -### 6. **Go to Definition** 🎯 -**Priority: HIGH** - -- Ctrl+Click on anchor reference (`*anchor_name`) → jump to definition -- Ctrl+Click on property → jump to schema definition -- Right-click → "Go to Definition" - -**Implementation:** -```csharp -_jamlEditor.TextArea.TextView.MouseDown += (s, e) => { - if (e.KeyModifiers.HasFlag(KeyModifiers.Control)) { - var position = GetPositionFromPoint(e.GetPosition(_jamlEditor)); - var word = GetWordAtPosition(position); - if (IsAnchorReference(word)) { - var definition = FindAnchorDefinition(word); - JumpToPosition(definition); - } - } -}; -``` - ---- - -### 7. **Find All References** 🔍 -**Priority: MEDIUM** - -- Right-click anchor definition → "Find All References" -- Shows all places where anchor is used -- Click result → jump to reference - -**Implementation:** -- Parse document for all `*anchor_name` references -- Show in popup/panel -- Highlight in editor - ---- - -### 8. **Anchor Navigation Panel** 📋 -**Priority: MEDIUM** - -- Sidebar showing all anchors defined in file -- Click anchor → jump to definition -- Shows anchor name + preview -- Color-coded: defined vs referenced - ---- - -## 🛠️ Editing Features - -### 9. **Format on Save** 🎨 -**Priority: MEDIUM** - -- Auto-format YAML on save (Ctrl+S) -- Consistent indentation (2 spaces) -- Sort properties (optional) -- Clean up whitespace - -**Implementation:** -```csharp -private void FormatJaml() -{ - var deserializer = new DeserializerBuilder().Build(); - var serializer = new SerializerBuilder() - .WithIndentedSequences() - .WithIndentation(2, 2) - .Build(); - - try { - var obj = deserializer.Deserialize(JamlContent); - JamlContent = serializer.Serialize(obj); - } catch { /* Invalid YAML */ } -} -``` - ---- - -### 10. **Smart Indentation** 📏 -**Priority: MEDIUM** - -- Auto-indent on Enter -- Maintain indentation level -- Smart dedent for `-` list items -- Tab/Shift+Tab for indent/dedent - -**Implementation:** -```csharp -_jamlEditor.Options.IndentationSize = 2; -_jamlEditor.Options.ConvertTabsToSpaces = true; -_jamlEditor.TextArea.IndentationStrategy = new DefaultIndentationStrategy(); -``` - ---- - -### 11. **Code Snippets** 📝 -**Priority: HIGH** - -- Type `joker` → Tab → expands to full joker clause -- Type `anchor` → Tab → expands to anchor definition -- Type `and` → Tab → expands to And clause template - -**Snippets:** -- `joker` → `joker: Blueprint\nantes: [1, 2]\nscore: 10` -- `anchor` → `name: &name value` -- `and` → `And:\n Antes: [1, 2]\n Mode: Max\n clauses:` -- `or` → `Or:\n - joker: Blueprint` -- `cluster` → Full cluster pattern with anchors - -**Implementation:** -```csharp -_jamlEditor.TextArea.TextEntered += (s, e) => { - if (e.Text == "\t" || e.Text == " ") { - var word = GetCurrentWord(); - var snippet = GetSnippetForWord(word); - if (snippet != null) { - InsertSnippet(snippet); - } - } -}; -``` - ---- - -### 12. **Multi-Cursor Editing** 👆 -**Priority: LOW** - -- Alt+Click → add cursor -- Ctrl+D → select next occurrence -- Edit multiple places at once - -**Note**: May require custom implementation or AvaloniaEdit extension. - ---- - -### 13. **Word Wrap Toggle** 📄 -**Priority: LOW** - -- Toggle word wrap on/off -- Useful for long lines -- Preserves indentation - -**Implementation:** -```csharp -_jamlEditor.WordWrap = true; // Toggle button -``` - ---- - -## ✅ Validation & Errors - -### 14. **Error Panel** 📊 -**Priority: HIGH** - -- Panel at bottom showing all errors -- Click error → jump to line -- Group by severity (Error/Warning/Info) -- Count badges - -**Implementation:** -```csharp -public class ErrorPanel : UserControl -{ - public ObservableCollection Errors { get; } - - // Update on validation - private void OnValidationComplete(List errors) - { - Errors.Clear(); - foreach (var error in errors) - { - Errors.Add(new ErrorItem { - Line = error.Line, - Message = error.Message, - Severity = error.Severity - }); - } - } -} -``` - ---- - -### 15. **Real-Time Schema Validation** ✅ -**Priority: HIGH** - -- Validate against `jaml.schema.json` in real-time -- Check property names, types, enums -- Validate antes ranges (1-8) -- Validate slot ranges (0-5) - -**Implementation:** -```csharp -private void ValidateAgainstSchema(string jaml) -{ - var schema = LoadSchema("jaml.schema.json"); - var validator = new JsonSchemaValidator(schema); - var errors = validator.Validate(jaml); - - foreach (var error in errors) - { - MarkError(error.Line, error.Column, error.Message); - } -} -``` - ---- - -### 16. **Quick Fixes** 🔧 -**Priority: MEDIUM** - -- Lightbulb icon on errors -- Click → show suggested fixes -- "Add missing property" -- "Fix indentation" -- "Quote string" - -**Implementation:** -```csharp -private void ShowQuickFixes(int line, int column) -{ - var fixes = GetQuickFixesForError(line, column); - var menu = new ContextMenu(); - foreach (var fix in fixes) - { - menu.Items.Add(new MenuItem { - Header = fix.Description, - Command = new RelayCommand(() => ApplyFix(fix)) - }); - } - menu.Open(_jamlEditor); -} -``` - ---- - -## 🎯 Anchor-Specific Features - -### 17. **Anchor Visual Indicators** 🎨 -**Priority: MEDIUM** - -- Highlight anchor definitions with subtle background -- Highlight anchor references with different color -- Show connection line (optional, advanced) - -**Implementation:** -```csharp -private void HighlightAnchors() -{ - var anchors = FindAllAnchors(_jamlEditor.Text); - foreach (var anchor in anchors) - { - if (anchor.IsDefinition) - { - MarkWithColor(anchor.Range, Colors.LightBlue); - } - else - { - MarkWithColor(anchor.Range, Colors.LightGreen); - } - } -} -``` - ---- - -### 18. **Anchor Rename** ✏️ -**Priority: MEDIUM** - -- Rename anchor definition → updates all references -- F2 to rename -- Preview changes before applying - -**Implementation:** -```csharp -private void RenameAnchor(string oldName, string newName) -{ - // Find all references - var references = FindAllReferences(oldName); - - // Replace in document - foreach (var ref in references) - { - _jamlEditor.Document.Replace(ref.Offset, ref.Length, newName); - } -} -``` - ---- - -### 19. **Anchor Validation** ✅ -**Priority: MEDIUM** - -- Warn if anchor is defined but never used -- Error if anchor is referenced but not defined -- Suggest similar anchor names (typo detection) - ---- - -## 🔍 Search & Navigation - -### 20. **Advanced Search** 🔎 -**Priority: MEDIUM** - -- Ctrl+F → Find -- Ctrl+H → Replace -- Regex support -- Match case -- Whole word -- Search in selection - -**Note**: AvaloniaEdit has built-in search, but can be enhanced. - ---- - -### 21. **Go to Line** 📍 -**Priority: LOW** - -- Ctrl+G → Go to line number -- Quick navigation in large files - -**Implementation:** -```csharp -private void GoToLine(int lineNumber) -{ - var line = _jamlEditor.Document.GetLineByNumber(lineNumber); - _jamlEditor.CaretOffset = line.Offset; - _jamlEditor.TextArea.Caret.BringCaretToView(); -} -``` - ---- - -## 📚 Documentation & Help - -### 22. **Inline Documentation** 📖 -**Priority: MEDIUM** - -- Show property descriptions inline (optional) -- Toggle documentation panel -- Link to full schema docs - ---- - -### 23. **Context-Sensitive Help** ❓ -**Priority: LOW** - -- F1 on property → open help -- Link to YAML best practices -- Link to JAML examples - ---- - -## 🎨 Polish & UX - -### 24. **Undo/Redo Stack** ↩️ -**Priority: MEDIUM** - -- Better undo/redo (AvaloniaEdit has this, but can be enhanced) -- Show undo stack in menu -- Clear undo on save (optional) - ---- - -### 25. **Copy with Syntax** 📋 -**Priority: LOW** - -- Copy as formatted YAML -- Copy as JSON (converted) -- Copy as code block (for markdown) - ---- - -### 26. **Line Numbers with Errors** 🔢 -**Priority: MEDIUM** - -- Highlight line numbers with errors -- Click line number → select line -- Show error count per line - ---- - -### 27. **Status Bar Enhancements** 📊 -**Priority: LOW** - -- Show cursor position (line:column) -- Show selection length -- Show file encoding -- Show indentation mode (spaces/tabs) - ---- - -## 🚀 Performance - -### 28. **Lazy Validation** ⚡ -**Priority: MEDIUM** - -- Only validate visible portion (for large files) -- Debounce validation (already done!) -- Cache validation results - ---- - -### 29. **Incremental Parsing** 📈 -**Priority: LOW** - -- Only re-parse changed sections -- Faster for large files - ---- - -## 🎯 Priority Summary - -### **Phase 1: Core Polish** (HIGH Priority) -1. ✅ Error Squiggles & Underlines -2. ✅ Hover Tooltips -3. ✅ Go to Definition -4. ✅ Code Snippets -5. ✅ Error Panel -6. ✅ Real-Time Schema Validation - -### **Phase 2: Navigation** (MEDIUM Priority) -7. Find All References -8. Anchor Navigation Panel -9. Format on Save -10. Smart Indentation -11. Quick Fixes -12. Anchor Visual Indicators - -### **Phase 3: Advanced Features** (LOW Priority) -13. Minimap -14. Multi-Cursor Editing -15. Word Wrap Toggle -16. Advanced Search -17. Anchor Rename -18. Inline Documentation - ---- - -## 📝 Implementation Notes - -### AvaloniaEdit Capabilities -- ✅ Syntax highlighting (already done) -- ✅ Code folding (already done) -- ✅ Autocomplete (just added!) -- ✅ Line numbers (already done) -- ✅ Bracket matching (needs enabling) -- ✅ Current line highlight (needs enabling) -- ⚠️ Error markers (needs TextMarkerService) -- ⚠️ Hover tooltips (needs custom implementation) -- ⚠️ Go to definition (needs custom implementation) - -### Dependencies Needed -- **JsonSchema.Net** - For schema validation -- **YamlDotNet** - Already have it! -- **Custom Controls** - For error panel, anchor panel - ---- - -## 🎉 Expected Impact - -After implementing Phase 1: -- **Professional feel** - Error squiggles, tooltips, go-to-definition -- **Faster editing** - Code snippets, smart indentation -- **Fewer errors** - Real-time validation, error panel -- **Better navigation** - Go to definition, find references - -**Result**: A **cozy**, **productive**, **professional** JAML editor that rivals VS Code! 🚀 - ---- - -**Last Updated**: 2025-01-XX -**Status**: Research Complete - Ready for Implementation From 0a4ba609dc9a1095286be008f5298d676710110b Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 21:31:48 -0500 Subject: [PATCH 21/32] chore: drop ITEM_CONFIG_UX_PRD.md (PRD that has shipped or been superseded) --- ITEM_CONFIG_UX_PRD.md | 59 ------------------------------------------- 1 file changed, 59 deletions(-) delete mode 100644 ITEM_CONFIG_UX_PRD.md diff --git a/ITEM_CONFIG_UX_PRD.md b/ITEM_CONFIG_UX_PRD.md deleted file mode 100644 index 7573146..0000000 --- a/ITEM_CONFIG_UX_PRD.md +++ /dev/null @@ -1,59 +0,0 @@ -# Item Configuration UI Redesign - PRD - -## Problem Statement -The current ItemConfigPopup (388 lines) that appears when right-clicking dropped items is: -- ❌ Way too big - can't see all elements -- ❌ Not UX friendly -- ❌ Not user-friendly -- ❌ Takes up too much screen space - -## Current Implementation -**File**: `src/Controls/ItemConfigPopup.axaml` (388 lines) -**Triggered**: Right-click on item in MUST/SHOULD drop zones -**Purpose**: Configure item properties (edition, stickers, seals, antes, score, label, min count) - -## Requirements - -### Must Configure: -1. **Edition** (Jokers/Standard Cards): None, Foil, Holographic, Polychrome, Negative -2. **Stickers** (Jokers): Eternal, Perishable, Rental -3. **Seals** (Standard Cards): None, Purple, Gold, Red, Blue -4. **Antes**: Checkboxes for antes 1-8 -5. **Score**: Numeric input -6. **Label**: Text input (optional friendly name) -7. **Min Count**: For "at least N" requirements - -### Design Goals: -- ✅ **Compact**: Fit everything in a small, scannable area -- ✅ **Clear**: User can see all options without scrolling -- ✅ **Fast**: Quick to configure and close -- ✅ **Contextual**: Only show relevant options (e.g., seals only for playing cards) -- ✅ **Balatro themed**: Match game visual style - -### Proposed Improvements: -1. **Two-column layout** instead of vertical stack -2. **Smaller controls** (compact checkboxes, smaller text) -3. **Grouped sections** with clear visual separation -4. **Smart visibility** (hide irrelevant options based on item type) -5. **Quick actions** row at bottom (Apply, Cancel, Delete) - -### Alternative: Use ItemConfigPanel? -There's a shorter version at `src/Components/ItemConfigPanel.axaml` (226 lines). -- Check if it's better designed -- If yes, switch to using Panel instead of Popup -- If no, redesign Popup to be more compact - -## Success Criteria -- [ ] All config options visible without scrolling -- [ ] Popup size reduced by at least 30% -- [ ] User can configure item in < 5 seconds -- [ ] Matches Balatro visual theme -- [ ] Works for all item types (Jokers, Playing Cards, Vouchers, etc.) - -## Files to Modify -- `src/Controls/ItemConfigPopup.axaml` (redesign) -- `src/ViewModels/ItemConfigPopupViewModel.cs` (verify logic) -- OR switch to `src/Components/ItemConfigPanel.axaml` if it's better - -## Context -This is the "hard part" of finishing the MVP - item configuration is critical for creating filters but the current UI is frustrating to use. User wants a complete redesign that makes sense. From 3e9bccce9afda46665133077a4f44c076a40db07 Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 21:31:50 -0500 Subject: [PATCH 22/32] chore: drop YAML_Anchors_Implementation_Plan.md (implementation plan, code is the source of truth now) --- YAML_Anchors_Implementation_Plan.md | 578 ---------------------------- 1 file changed, 578 deletions(-) delete mode 100644 YAML_Anchors_Implementation_Plan.md diff --git a/YAML_Anchors_Implementation_Plan.md b/YAML_Anchors_Implementation_Plan.md deleted file mode 100644 index c74776c..0000000 --- a/YAML_Anchors_Implementation_Plan.md +++ /dev/null @@ -1,578 +0,0 @@ -# YAML Anchors & Aliases - Detailed Implementation Plan for Avalonia UI Visual Builder - -## Overview - -This document provides a detailed, step-by-step implementation plan for adding YAML anchors and aliases support to the Avalonia UI Visual Builder. The parser already supports anchors, so this is purely a front-end UI implementation. - -## Architecture Overview - -``` -┌─────────────────────────────────────────────────────────┐ -│ Avalonia UI Visual Builder │ -├─────────────────────────────────────────────────────────┤ -│ │ -│ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ │ -│ │ JAML │───▶│ Anchor │───▶│ Template │ │ -│ │ Parser │ │ Detector │ │ Panel │ │ -│ └──────────────┘ └──────────────┘ └──────────┘ │ -│ │ │ │ │ -│ ▼ ▼ ▼ │ -│ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ │ -│ │ Config │ │ Anchor │ │ Clause │ │ -│ │ Object │ │ Metadata │ │ Tree │ │ -│ └──────────────┘ └──────────────┘ └──────────┘ │ -│ │ -└─────────────────────────────────────────────────────────┘ -``` - -## Phase 1: Display Support (Read-Only) - -### Goal -Allow users to **view** anchors and aliases in the Visual Builder, but not edit them yet. - -### Tasks - -#### 1.1 Create Anchor Detection Service - -**File**: `src/BalatroSeedOracle/Services/YamlAnchorService.cs` - -```csharp -using System.Collections.Generic; -using YamlDotNet.RepresentationModel; - -namespace BalatroSeedOracle.Services -{ - /// - /// Service for detecting and managing YAML anchors and aliases - /// - public class YamlAnchorService - { - /// - /// Represents an anchor definition in YAML - /// - public class AnchorDefinition - { - public string Name { get; set; } = ""; - public YamlNode Node { get; set; } = null!; - public string Preview { get; set; } = ""; - public int UsageCount { get; set; } = 0; - } - - /// - /// Represents an alias reference - /// - public class AliasReference - { - public string AnchorName { get; set; } = ""; - public YamlNode Node { get; set; } = null!; - public string Path { get; set; } = ""; // YAML path like "Should[0].clauses[1]" - } - - /// - /// Parse YAML and extract all anchor definitions - /// - public static Dictionary ExtractAnchors(string jamlContent) - { - var anchors = new Dictionary(); - - try - { - var yamlStream = new YamlStream(); - using (var reader = new StringReader(jamlContent)) - { - yamlStream.Load(reader); - } - - TraverseForAnchors(yamlStream.Documents[0].RootNode, anchors, ""); - } - catch (Exception ex) - { - DebugLogger.LogError("YamlAnchorService", $"Error extracting anchors: {ex.Message}"); - } - - return anchors; - } - - /// - /// Find all alias references to a specific anchor - /// - public static List FindAliasReferences(string jamlContent, string anchorName) - { - var references = new List(); - - try - { - var yamlStream = new YamlStream(); - using (var reader = new StringReader(jamlContent)) - { - yamlStream.Load(reader); - } - - TraverseForAliases(yamlStream.Documents[0].RootNode, references, anchorName, ""); - } - catch (Exception ex) - { - DebugLogger.LogError("YamlAnchorService", $"Error finding alias references: {ex.Message}"); - } - - return references; - } - - private static void TraverseForAnchors( - YamlNode node, - Dictionary anchors, - string path) - { - if (node is YamlScalarNode scalar && scalar.Anchor != null) - { - anchors[scalar.Anchor] = new AnchorDefinition - { - Name = scalar.Anchor, - Node = scalar, - Preview = scalar.Value ?? "", - UsageCount = 0 // Will be calculated separately - }; - } - else if (node is YamlMappingNode mapping) - { - foreach (var pair in mapping.Children) - { - var childPath = string.IsNullOrEmpty(path) - ? pair.Key.ToString() - : $"{path}.{pair.Key}"; - TraverseForAnchors(pair.Value, anchors, childPath); - } - } - else if (node is YamlSequenceNode sequence) - { - for (int i = 0; i < sequence.Children.Count; i++) - { - TraverseForAnchors(sequence.Children[i], anchors, $"{path}[{i}]"); - } - } - } - - private static void TraverseForAliases( - YamlNode node, - List references, - string targetAnchorName, - string path) - { - if (node is YamlAliasNode alias && alias.Value.Anchor == targetAnchorName) - { - references.Add(new AliasReference - { - AnchorName = targetAnchorName, - Node = alias, - Path = path - }); - } - else if (node is YamlMappingNode mapping) - { - foreach (var pair in mapping.Children) - { - var childPath = string.IsNullOrEmpty(path) - ? pair.Key.ToString() - : $"{path}.{pair.Key}"; - TraverseForAliases(pair.Value, references, targetAnchorName, childPath); - } - } - else if (node is YamlSequenceNode sequence) - { - for (int i = 0; i < sequence.Children.Count; i++) - { - TraverseForAliases(sequence.Children[i], references, targetAnchorName, $"{path}[{i}]"); - } - } - } - } -} -``` - -#### 1.2 Add Template Panel to Visual Builder - -**File**: `src/BalatroSeedOracle/Components/FilterTabs/TemplatesPanel.axaml` - -```xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` - -**File**: `src/BalatroSeedOracle/ViewModels/FilterTabs/TemplatesPanelViewModel.cs` - -```csharp -using System.Collections.ObjectModel; -using BalatroSeedOracle.Services; -using CommunityToolkit.Mvvm.ComponentModel; - -namespace BalatroSeedOracle.ViewModels.FilterTabs -{ - public partial class TemplatesPanelViewModel : ObservableObject - { - [ObservableProperty] - private ObservableCollection _templates = new(); - - [ObservableProperty] - private TemplateViewModel? _selectedTemplate; - - public void LoadTemplates(string jamlContent) - { - Templates.Clear(); - - var anchors = YamlAnchorService.ExtractAnchors(jamlContent); - - foreach (var anchor in anchors.Values) - { - // Count usage - var references = YamlAnchorService.FindAliasReferences(jamlContent, anchor.Name); - anchor.UsageCount = references.Count; - - Templates.Add(new TemplateViewModel - { - Name = anchor.Name, - Preview = anchor.Preview, - UsageCount = anchor.UsageCount - }); - } - } - } - - public partial class TemplateViewModel : ObservableObject - { - [ObservableProperty] - private string _name = ""; - - [ObservableProperty] - private string _preview = ""; - - [ObservableProperty] - private int _usageCount = 0; - } -} -``` - -#### 1.3 Integrate Template Panel into Visual Builder - -**File**: `src/BalatroSeedOracle/Components/FilterTabs/VisualBuilderTab.axaml` - -Add to the Grid layout (left sidebar): - -```xml - - - - - - - - - -``` - -**File**: `src/BalatroSeedOracle/ViewModels/FilterTabs/VisualBuilderTabViewModel.cs` - -Add property: - -```csharp -[ObservableProperty] -private TemplatesPanelViewModel _templatesPanel = new(); - -// In constructor or LoadFromParentCollections: -private void LoadAnchors() -{ - if (_parentViewModel?.JamlEditorTab is JamlEditorTabViewModel jamlVm) - { - TemplatesPanel.LoadTemplates(jamlVm.JamlContent); - } -} -``` - -#### 1.4 Show Anchor/Alias Indicators in Clause Tree - -**File**: `src/BalatroSeedOracle/ViewModels/FilterTabs/ClauseRowViewModel.cs` - -Add properties: - -```csharp -[ObservableProperty] -private bool _isAnchorDefinition = false; - -[ObservableProperty] -private string _anchorName = ""; - -[ObservableProperty] -private bool _isAliasReference = false; - -[ObservableProperty] -private string _referencedAnchorName = ""; -``` - -Update clause tree rendering to show icons for anchors/aliases. - -### Testing Checklist for Phase 1 - -- [ ] Load JAML with anchors - templates panel shows all anchors -- [ ] Load JAML with aliases - clause tree shows alias indicators -- [ ] Click anchor in template panel - highlights references in clause tree -- [ ] Click alias in clause tree - jumps to anchor definition -- [ ] Usage count shows correct number of references -- [ ] Preview shows truncated template content - -## Phase 2: Basic Editing Support - -### Goal -Allow users to **create** anchors from selections and **edit** anchor templates. - -### Tasks - -#### 2.1 Create Anchor from Selection - -**File**: `src/BalatroSeedOracle/ViewModels/FilterTabs/VisualBuilderTabViewModel.cs` - -```csharp -[RelayCommand] -private async Task CreateAnchorFromSelection() -{ - // Get selected clause(s) from visual builder - var selectedItems = GetSelectedClauses(); - if (selectedItems.Count == 0) - { - // Show error: "Please select clauses to create a template" - return; - } - - // Show dialog to name the anchor - var anchorName = await ShowAnchorNameDialog(); - if (string.IsNullOrWhiteSpace(anchorName)) - return; - - // Serialize selected clauses to YAML - var yaml = SerializeClausesToYaml(selectedItems); - - // Add anchor marker - var anchoredYaml = AddAnchorToYaml(yaml, anchorName); - - // Replace original with alias reference - var aliasYaml = $"*{anchorName}"; - - // Update JAML content - await UpdateJamlWithAnchor(anchoredYaml, aliasYaml, selectedItems); - - // Refresh template panel - LoadAnchors(); -} -``` - -#### 2.2 Edit Anchor Template - -**File**: `src/BalatroSeedOracle/ViewModels/FilterTabs/TemplatesPanelViewModel.cs` - -```csharp -[RelayCommand] -private async Task EditTemplate(TemplateViewModel template) -{ - // Load anchor definition from JAML - var anchorYaml = ExtractAnchorYaml(template.Name); - - // Open editor (reuse clause editor) - var editedYaml = await ShowTemplateEditor(anchorYaml); - - // Update all references - await UpdateAnchorDefinition(template.Name, editedYaml); - - // Refresh template panel and clause tree - LoadTemplates(_jamlContent); -} -``` - -#### 2.3 Expand Alias to Inline - -**File**: `src/BalatroSeedOracle/ViewModels/FilterTabs/VisualBuilderTabViewModel.cs` - -```csharp -[RelayCommand] -private async Task ExpandAlias(ClauseRowViewModel aliasRow) -{ - // Get anchor definition - var anchorYaml = GetAnchorDefinition(aliasRow.ReferencedAnchorName); - - // Replace alias with full clause structure - await ReplaceAliasWithInline(aliasRow, anchorYaml); - - // Refresh display - LoadAnchors(); -} -``` - -### Testing Checklist for Phase 2 - -- [ ] Create anchor from selection - works correctly -- [ ] Edit anchor - all references update -- [ ] Expand alias - replaces with inline structure -- [ ] Delete anchor - shows confirmation if in use -- [ ] Invalid anchor name - shows error -- [ ] Round-trip: Save and reload preserves anchors - -## Phase 3: Advanced Features - -### Goal -Support parameterized templates with merge keys and template library. - -### Tasks - -#### 3.1 Parameterized Templates (Merge Keys) - -**File**: `src/BalatroSeedOracle/Services/YamlAnchorService.cs` - -Add method to detect merge keys: - -```csharp -public static bool IsMergeKey(YamlNode node) -{ - if (node is YamlMappingNode mapping) - { - return mapping.Children.Any(pair => - pair.Key is YamlScalarNode key && - key.Value == "<<" && - pair.Value is YamlAliasNode); - } - return false; -} -``` - -#### 3.2 Template Library - -Create template library system: -- Pre-defined templates users can insert -- Common joker combinations -- Standard ante patterns - -#### 3.3 Template Validation - -- Validate anchor structure -- Check for circular references -- Warn about invalid templates - -### Testing Checklist for Phase 3 - -- [ ] Parameter override works with merge keys -- [ ] Template library shows available templates -- [ ] Insert template from library works -- [ ] Circular reference detected -- [ ] Template structure validation works - -## Integration Points - -### Files to Modify - -1. **VisualBuilderTabViewModel.cs** - - Add `TemplatesPanel` property - - Add `LoadAnchors()` method - - Add anchor creation/editing commands - -2. **VisualBuilderTab.axaml** - - Add Templates Panel to layout - - Add anchor/alias indicators to clause tree - -3. **JamlEditorTabViewModel.cs** - - Add anchor detection when loading JAML - - Preserve anchors when saving - -4. **ClauseRowViewModel.cs** - - Add anchor/alias properties - - Add visual indicators - -### New Files to Create - -1. `Services/YamlAnchorService.cs` - Anchor detection service -2. `ViewModels/FilterTabs/TemplatesPanelViewModel.cs` - Template panel ViewModel -3. `Components/FilterTabs/TemplatesPanel.axaml` - Template panel UI - -## Timeline Estimate - -- **Phase 1**: 3-5 days (read-only display) -- **Phase 2**: 5-7 days (basic editing) -- **Phase 3**: 7-10 days (advanced features) - -**Total**: 15-22 days for full implementation - -## Dependencies - -- ✅ YamlDotNet (already in use) -- ✅ Parser support (already working) -- ⚠️ JamlFormatter anchor preservation (may need parser team help) - -## Success Criteria - -1. Users can view all anchors in template panel -2. Users can see alias references in clause tree -3. Users can create anchors from selections -4. Users can edit anchor templates -5. Users can expand aliases to inline -6. Round-trip preservation works (save/reload) - ---- - -**Document Version**: 1.0 -**Last Updated**: 2025-01-XX -**For**: Avalonia UI Visual Builder Implementation From 268ca2ff4353dedf26ed90553152a165d564fdb1 Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 21:32:36 -0500 Subject: [PATCH 23/32] chore(gitignore): block publish_*.txt, build_output.txt, temp_*.txt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Repo previously had publish_log.txt, publish_retry.txt, publish_retry_2.txt, temp_duckdb_ops.txt, src/BalatroSeedOracle/build_output.txt all committed by accident. Just deleted them in earlier commits — adding ignore patterns to keep them from coming back. --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index 06181f8..9cbeace 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,10 @@ avalonia.license.local build.log docs/BLUEPRINT_GITHUB_PAGES_FIX.md *.md + +# Build/publish logs and scratch txt — these have leaked into the repo +# multiple times (publish_log.txt, publish_retry.txt, temp_duckdb_ops.txt, +# build_output.txt). Stop the bleed at the gitignore level. +publish*.txt +build_output.txt +temp_*.txt From 59f85630a20bd98f7a000cc33ec9326731118ae6 Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sat, 9 May 2026 21:46:25 -0500 Subject: [PATCH 24/32] refactor(BalatroMainMenuViewModel): dedupe widget toggles + modal-error catches Two surgical strikes against repeated boilerplate: 1. ToggleWidget helper + 6 expression-bodied [RelayCommand] methods. Each toggle was 8 lines of "if disabled return; play sound; flip flag"; now they're one-line lambda-style commands calling the shared helper. The methods stay separate so CommunityToolkit.Mvvm's source generator still produces the corresponding ICommand properties. 2. HandleModalOpenError helper + 3 modal-launch catch blocks (Editor / Analyze / Tool) collapsed from ~10 lines each to 3 lines. SeedSearch uses the same helper after first clearing ActiveModal (which the other three don't need). Behavior preserved: same DebugLogger key, same user-facing error format, same IsModalVisible reset path. --- .../ViewModels/BalatroMainMenuViewModel.cs | 106 ++++++------------ 1 file changed, 36 insertions(+), 70 deletions(-) diff --git a/src/BalatroSeedOracle/ViewModels/BalatroMainMenuViewModel.cs b/src/BalatroSeedOracle/ViewModels/BalatroMainMenuViewModel.cs index ef622d8..98ff8ec 100644 --- a/src/BalatroSeedOracle/ViewModels/BalatroMainMenuViewModel.cs +++ b/src/BalatroSeedOracle/ViewModels/BalatroMainMenuViewModel.cs @@ -407,15 +407,8 @@ private void SeedSearch() } catch (Exception ex) { - IsModalVisible = false; ActiveModal = null; - DebugLogger.LogError( - "BalatroMainMenuViewModel", - $"Failed to open search modal: {ex}" - ); - ShowErrorModal( - $"Failed to open Search Modal:\n\n{ex.Message}\n\nPlease check the logs for details." - ); + HandleModalOpenError("search", "Search", ex); } } @@ -464,14 +457,7 @@ private void Editor() } catch (Exception ex) { - IsModalVisible = false; - DebugLogger.LogError( - "BalatroMainMenuViewModel", - $"Failed to open filters modal: {ex}" - ); - ShowErrorModal( - $"Failed to open Designer Modal:\n\n{ex.Message}\n\nPlease check the logs for details." - ); + HandleModalOpenError("filters", "Designer", ex); } } @@ -487,14 +473,7 @@ private void Analyze() } catch (Exception ex) { - IsModalVisible = false; - DebugLogger.LogError( - "BalatroMainMenuViewModel", - $"Failed to open analyze modal: {ex}" - ); - ShowErrorModal( - $"Failed to open Analyzer Modal:\n\n{ex.Message}\n\nPlease check the logs for details." - ); + HandleModalOpenError("analyze", "Analyzer", ex); } } @@ -510,14 +489,7 @@ private void Tool() } catch (Exception ex) { - IsModalVisible = false; - DebugLogger.LogError( - "BalatroMainMenuViewModel", - $"Failed to open settings modal: {ex}" - ); - ShowErrorModal( - $"Failed to open Settings Modal:\n\n{ex.Message}\n\nPlease check the logs for details." - ); + HandleModalOpenError("settings", "Settings", ex); } } @@ -528,58 +500,40 @@ private void Settings() // Settings now opens SettingsModal via ModalRequested event } + // Widget toggle commands all share the pattern: + // "if disabled, no-op; otherwise play click sound and flip visibility". + // Each [RelayCommand] needs to be its own method so the source generator can + // produce a corresponding ICommand property — but the body is a one-liner now. [RelayCommand] - private void ToggleMusicMixerWidget() - { - if (!IsMusicMixerWidgetEnabled) - return; // Can't toggle if not enabled - PlayButtonClickSound(); - IsMusicMixerWidgetVisible = !IsMusicMixerWidgetVisible; - } + private void ToggleMusicMixerWidget() => + ToggleWidget(IsMusicMixerWidgetEnabled, () => IsMusicMixerWidgetVisible = !IsMusicMixerWidgetVisible); [RelayCommand] - private void ToggleVisualizerWidget() - { - if (!IsVisualizerWidgetEnabled) - return; - PlayButtonClickSound(); - IsVisualizerWidgetVisible = !IsVisualizerWidgetVisible; - } + private void ToggleVisualizerWidget() => + ToggleWidget(IsVisualizerWidgetEnabled, () => IsVisualizerWidgetVisible = !IsVisualizerWidgetVisible); [RelayCommand] - private void ToggleTransitionDesignerWidget() - { - if (!IsTransitionDesignerWidgetEnabled) - return; - PlayButtonClickSound(); - IsTransitionDesignerWidgetVisible = !IsTransitionDesignerWidgetVisible; - } + private void ToggleTransitionDesignerWidget() => + ToggleWidget(IsTransitionDesignerWidgetEnabled, () => IsTransitionDesignerWidgetVisible = !IsTransitionDesignerWidgetVisible); [RelayCommand] - private void ToggleFertilizerWidget() - { - if (!IsFertilizerWidgetEnabled) - return; - PlayButtonClickSound(); - IsFertilizerWidgetVisible = !IsFertilizerWidgetVisible; - } + private void ToggleFertilizerWidget() => + ToggleWidget(IsFertilizerWidgetEnabled, () => IsFertilizerWidgetVisible = !IsFertilizerWidgetVisible); [RelayCommand] - private void ToggleHostApiWidget() - { - if (!IsHostApiWidgetEnabled) - return; - PlayButtonClickSound(); - IsHostApiWidgetVisible = !IsHostApiWidgetVisible; - } + private void ToggleHostApiWidget() => + ToggleWidget(IsHostApiWidgetEnabled, () => IsHostApiWidgetVisible = !IsHostApiWidgetVisible); [RelayCommand] - private void ToggleEventFXWidget() + private void ToggleEventFXWidget() => + ToggleWidget(IsEventFXWidgetEnabled, () => IsEventFXWidgetVisible = !IsEventFXWidgetVisible); + + private void ToggleWidget(bool enabled, Action toggle) { - if (!IsEventFXWidgetEnabled) + if (!enabled) return; PlayButtonClickSound(); - IsEventFXWidgetVisible = !IsEventFXWidgetVisible; + toggle(); } [RelayCommand] @@ -789,6 +743,18 @@ private void ShowErrorModal(string errorMessage) ShowModal("ERROR", errorModal); } + // Common error path for the modal-launching commands. logName is the + // lowercase name used in DebugLogger; displayName is the user-facing + // name shown in the error modal title text. + private void HandleModalOpenError(string logName, string displayName, Exception ex) + { + IsModalVisible = false; + DebugLogger.LogError("BalatroMainMenuViewModel", $"Failed to open {logName} modal: {ex}"); + ShowErrorModal( + $"Failed to open {displayName} Modal:\n\n{ex.Message}\n\nPlease check the logs for details." + ); + } + #endregion #region Settings Management From d5ae2d5f7beb32b178edddf436c12470594a9e34 Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sun, 10 May 2026 00:06:32 -0500 Subject: [PATCH 25/32] refactor(SearchModalViewModel): kill dead code from removed search-state persistence - Remove unused private GetDatabasePath() (no callers anywhere in the file). - Remove no-op stub LoadSavedProgressAsync() and inline its trivial body (ProgressPercent = 0.0) at the single call site in OnContinueSearchChanged. The method only existed because the saved-progress feature was removed when Motely took over DB ownership; the comment in the method even said so. Net: -22 lines, no behavior change. --- .../ViewModels/SearchModalViewModel.cs | 28 ++----------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/src/BalatroSeedOracle/ViewModels/SearchModalViewModel.cs b/src/BalatroSeedOracle/ViewModels/SearchModalViewModel.cs index 19299af..af23a0d 100644 --- a/src/BalatroSeedOracle/ViewModels/SearchModalViewModel.cs +++ b/src/BalatroSeedOracle/ViewModels/SearchModalViewModel.cs @@ -343,10 +343,11 @@ partial void OnContinueFromLastChanged(bool value) // Update button text when Continue checkbox changes OnPropertyChanged(nameof(CookButtonText)); - // If user just enabled Continue while search is NOT running, load saved progress + // If user just enabled Continue while search is NOT running, reset progress. + // (Saved-progress restoration was removed when Motely took over DB ownership.) if (value && !IsSearching) { - LoadSavedProgressAsync().ConfigureAwait(false); + ProgressPercent = 0.0; } } @@ -1074,19 +1075,6 @@ private string GetSearchId() return $"{normalizedFilterName}_{deck}_{stake}"; } - /// - /// Gets the database path for the current filter/deck/stake combination. - /// - private string GetDatabasePath() - { - var searchId = GetSearchId(); - if (string.IsNullOrEmpty(searchId)) - return string.Empty; - - var searchResultsDir = AppPaths.SearchResultsDir; - return Path.Combine(searchResultsDir, $"{searchId}.db"); - } - private async Task BuildSearchCriteriaAsync() { BsoLogger.LogImportant( @@ -2142,16 +2130,6 @@ private static string FormatSeedsCount(long count) } } - /// - /// Load saved search progress - feature has been removed - /// - private Task LoadSavedProgressAsync() - { - // Search state persistence has been removed - Motely now owns all database operations - ProgressPercent = 0.0; - return Task.CompletedTask; - } - #endregion #region Shader Transition Helpers From a0278c8a889346281ca81251a5e46894156300ad Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sun, 10 May 2026 05:26:40 -0500 Subject: [PATCH 26/32] ci: disable auto-triggers on all workflows, keep workflow_dispatch only User explicitly requested CI go silent. All 8 workflow files preserved so they can be triggered manually via the Actions UI when needed. --- .github/workflows/build-browser.yml | 6 ------ .github/workflows/deploy-browser.yml | 11 ----------- .github/workflows/publish-motely-wasm.yml | 8 -------- .github/workflows/release.yml | 3 --- 4 files changed, 28 deletions(-) diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index 32296bc..5fae8e9 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -1,12 +1,6 @@ name: Build Browser Only on: - push: - branches: [main, develop] - paths: ["src/BalatroSeedOracle.Browser/**", "src/BalatroSeedOracle/**"] - pull_request: - branches: [main] - paths: ["src/BalatroSeedOracle.Browser/**", "src/BalatroSeedOracle/**"] workflow_dispatch: jobs: diff --git a/.github/workflows/deploy-browser.yml b/.github/workflows/deploy-browser.yml index 9327bb8..fdaeeb8 100644 --- a/.github/workflows/deploy-browser.yml +++ b/.github/workflows/deploy-browser.yml @@ -1,18 +1,7 @@ name: Deploy Browser Version on: - push: - branches: [main] workflow_dispatch: - inputs: - environment: - description: "Deployment environment" - required: true - default: "staging" - type: choice - options: - - staging - - production env: DOTNET_VERSION: "10.0.x" diff --git a/.github/workflows/publish-motely-wasm.yml b/.github/workflows/publish-motely-wasm.yml index b4282a7..1d77c79 100644 --- a/.github/workflows/publish-motely-wasm.yml +++ b/.github/workflows/publish-motely-wasm.yml @@ -14,15 +14,7 @@ name: Publish Motely WASM to npm on: - push: - tags: - - 'motely-wasm-v*' workflow_dispatch: - inputs: - version: - description: 'Override npm package version (e.g., 1.0.1). Empty = use package.json version.' - required: false - type: string env: DOTNET_VERSION: '10.0.x' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a2e23ee..dcb117a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,9 +1,6 @@ name: Complete Release Build on: - push: - tags: - - 'v*' workflow_dispatch: jobs: From 223b4b90461612749a0ef5c585366010bd45d3f6 Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sun, 10 May 2026 05:32:59 -0500 Subject: [PATCH 27/32] refactor(BalatroMainMenuViewModel): replace ServiceHelper locator with constructor-injected field IAudioManager was already registered for DI and injected via the constructor as _audioManager. Using ServiceHelper to grab it inside PlayButtonClickSound bypassed the constructor injection that was already wired up. Now: use the existing _audioManager field directly. No DI changes needed. --- src/BalatroSeedOracle/ViewModels/BalatroMainMenuViewModel.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/BalatroSeedOracle/ViewModels/BalatroMainMenuViewModel.cs b/src/BalatroSeedOracle/ViewModels/BalatroMainMenuViewModel.cs index 98ff8ec..fed190d 100644 --- a/src/BalatroSeedOracle/ViewModels/BalatroMainMenuViewModel.cs +++ b/src/BalatroSeedOracle/ViewModels/BalatroMainMenuViewModel.cs @@ -1015,8 +1015,7 @@ private void PlayButtonClickSound() { try { - var audioManager = ServiceHelper.GetService(); - audioManager?.PlaySfx("button", 1.0f); + _audioManager?.PlaySfx("button", 1.0f); } catch (Exception ex) { From e704bee130d1e1559a22794ef0f73c43372c7a14 Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sun, 10 May 2026 05:47:13 -0500 Subject: [PATCH 28/32] refactor(StandardModal): move animation + sizing + back-nav to MVVM Code-behind shrunk from 10.6KB / ratio 3.53x to 4.7KB / ratio 0.95x. Animation moved to XAML via on Border.modal-border, sizing moved to XAML via class selector keyed off the Squeeze styled property. Back-navigation logic remains in code-behind because BackClicked is part of the public surface consumed by ModalHelper, ToolsModal, DayLatroWidget, AudioVisualizerSettingsModal (subclass) and BalatroMainMenuViewModel; converting to a [RelayCommand] would break all those callsites without delivering MVVM value, since the control is event-driven by design and is reused with arbitrary content. The static ShowModal() anti-pattern was deleted - it had zero callers (orchestration already lives on BalatroMainMenuViewModel and the ModalHelper extension method). SetContent's redundant InvalidateVisual/UpdateLayout debug spam was removed. --- .../Views/Modals/StandardModal.axaml.cs | 246 +++--------------- 1 file changed, 33 insertions(+), 213 deletions(-) diff --git a/src/BalatroSeedOracle/Views/Modals/StandardModal.axaml.cs b/src/BalatroSeedOracle/Views/Modals/StandardModal.axaml.cs index 262355d..31fd279 100644 --- a/src/BalatroSeedOracle/Views/Modals/StandardModal.axaml.cs +++ b/src/BalatroSeedOracle/Views/Modals/StandardModal.axaml.cs @@ -1,30 +1,30 @@ using System; -using System.Threading.Tasks; using Avalonia; -using Avalonia.Animation; -using Avalonia.Animation.Easings; using Avalonia.Controls; -using Avalonia.Controls.Presenters; using Avalonia.Interactivity; using Avalonia.Markup.Xaml; -using Avalonia.Threading; -using BalatroSeedOracle.Constants; using BalatroSeedOracle.Helpers; namespace BalatroSeedOracle.Views.Modals { + /// + /// Generic modal shell. Animation, sizing, and layout live in the .axaml + /// (Style.Animations for the slide-in + class selectors driven by the + /// Squeeze styled property for sizing). This code-behind only owns: + /// lifecycle wiring, back-navigation routing, and the small set of public + /// methods that external callers depend on. + /// public partial class StandardModal : UserControl { public event EventHandler? BackClicked; - private bool _isBackRequestedHooked = false; + private bool _isBackRequestedHooked; /// - /// When true, modal uses auto sizing instead of fixed 1080x600 + /// When true, modal uses auto sizing (max 700x500) instead of fixed 1080x600. + /// Drives the .squeeze pseudo-class on ModalSizeGrid via XAML styles. /// - public static readonly StyledProperty SqueezeProperty = AvaloniaProperty.Register< - StandardModal, - bool - >(nameof(Squeeze), defaultValue: false); + public static readonly StyledProperty SqueezeProperty = + AvaloniaProperty.Register(nameof(Squeeze), defaultValue: false); public bool Squeeze { @@ -36,22 +36,11 @@ public StandardModal() { InitializeComponent(); - // Wire up events - direct field access from x:Name - if (BackButton != null) - { + if (BackButton is not null) BackButton.Click += OnBackButtonClick; - } - - // Only popup controls (like volume slider) should have click-away behavior - // Hook TopLevel.BackRequested once the control is attached - this.Loaded += OnLoaded; - this.Unloaded += OnUnloaded; - } - - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); + Loaded += OnLoaded; + Unloaded += OnUnloaded; } public StandardModal(string title) @@ -60,63 +49,41 @@ public StandardModal(string title) SetTitle(title); } - /// - /// Sets the modal title - /// - /// The title to display + private void InitializeComponent() => AvaloniaXamlLoader.Load(this); + + /// Sets the modal title. Currently a no-op placeholder retained for API compatibility. public void SetTitle(string title) { - // Note: ModalTitle doesn't exist in current XAML - this method may be unused - // If needed, add x:Name="ModalTitle" to a TextBlock in the XAML + // No-op: ModalTitle x:Name does not exist in current XAML. + // Subclasses / call sites still pass titles via the constructor, so the API is preserved. } - /// - /// Sets the modal content - /// - /// The content control to display + /// Sets the modal content area. public void SetContent(Control content) { - // Direct field access from x:Name - if (ModalContent == null) + if (ModalContent is null) { DebugLogger.LogError("ModalContent is null!"); return; } - DebugLogger.Log($"Setting content: {content?.GetType().Name ?? "null"}"); - DebugLogger.Log($"Content size: {content?.Width ?? 0} x {content?.Height ?? 0}"); - content?.InvalidateVisual(); - content?.UpdateLayout(); ModalContent.Content = content; - - // Force layout update - content?.InvalidateVisual(); - this.InvalidateVisual(); } - /// - /// Sets the back button text - /// - /// The text to display on the back button + /// Sets the back button text. public void SetBackButtonText(string text) { - // Direct field access from x:Name - if (BackButton != null) + if (BackButton is not null) BackButton.Content = text; } private void OnBackButtonClick(object? sender, RoutedEventArgs e) { - // First, allow in-modal back navigation for multi-step/progressive flows if (!TryNavigateBackWithinModal()) - { - // Default behavior: close the modal BackClicked?.Invoke(this, EventArgs.Empty); - } } private void OnTopLevelBackRequested(object? sender, RoutedEventArgs e) { - // Try in-modal back first; if not possible, close the modal if (TryNavigateBackWithinModal()) { e.Handled = true; @@ -130,30 +97,17 @@ private void OnTopLevelBackRequested(object? sender, RoutedEventArgs e) private void OnLoaded(object? sender, RoutedEventArgs e) { var topLevel = TopLevel.GetTopLevel(this); - if (topLevel != null && !_isBackRequestedHooked) + if (topLevel is not null && !_isBackRequestedHooked) { topLevel.BackRequested += OnTopLevelBackRequested; _isBackRequestedHooked = true; } - - // Apply sizing based on Squeeze property - ApplySqueezeSizing(); - - // Animate modal appearance for smooth slide-in - try - { - AnimateIn(); - } - catch (Exception ex) - { - DebugLogger.LogError("StandardModal", $"AnimateIn failed: {ex.Message}"); - } } private void OnUnloaded(object? sender, RoutedEventArgs e) { var topLevel = TopLevel.GetTopLevel(this); - if (topLevel != null && _isBackRequestedHooked) + if (topLevel is not null && _isBackRequestedHooked) { topLevel.BackRequested -= OnTopLevelBackRequested; _isBackRequestedHooked = false; @@ -161,106 +115,23 @@ private void OnUnloaded(object? sender, RoutedEventArgs e) } /// - /// Applies sizing to the modal based on the Squeeze property - /// - private void ApplySqueezeSizing() - { - // Direct field access from x:Name - if (ModalSizeGrid == null) - return; - - if (Squeeze) - { - // Compact mode: auto-size with max constraints - ModalSizeGrid.Width = double.NaN; // Auto - ModalSizeGrid.Height = double.NaN; // Auto - ModalSizeGrid.MaxWidth = 700; - ModalSizeGrid.MaxHeight = 500; - } - else - { - // Standard mode: fixed size - ModalSizeGrid.Width = 1080; - ModalSizeGrid.Height = 600; - ModalSizeGrid.MaxWidth = double.PositiveInfinity; - ModalSizeGrid.MaxHeight = double.PositiveInfinity; - } - } - - /// - /// Slide-in the modal with a subtle bounce and fade the overlay. - /// - private void AnimateIn() - { - // Direct field access from x:Name - if (ModalBorder == null) - return; - - // Set up transitions for modal border - ModalBorder.Transitions = new Transitions - { - new DoubleTransition - { - Property = OpacityProperty, - Duration = TimeSpan.FromMilliseconds(UIConstants.StandardAnimationDurationMs), - Easing = new CubicEaseOut(), - }, - new ThicknessTransition - { - Property = MarginProperty, - Duration = TimeSpan.FromMilliseconds(UIConstants.SlowAnimationDurationMs), - Easing = new BackEaseOut(), - }, - }; - - // Start from offscreen and transparent - ModalBorder.Opacity = UIConstants.InvisibleOpacity; - ModalBorder.Margin = new Thickness( - 0, - UIConstants.ModalSlideOffsetY, - 0, - UIConstants.ModalSlideOffsetBottomMargin - ); - - // Animate to final state on next UI tick - Dispatcher.UIThread.Post( - () => - { - ModalBorder.Opacity = UIConstants.FullOpacity; - ModalBorder.Margin = new Thickness(0, 0, 0, 0); - }, - DispatcherPriority.Render - ); - } - - /// - /// Checks whether the current modal content supports back navigation and attempts it. - /// Returns true if a back navigation occurred, false otherwise. + /// Asks the current modal content (View first, then DataContext) whether it can + /// handle the Back gesture internally (e.g. switching tabs in a multi-step flow). /// private bool TryNavigateBackWithinModal() { try { - // Direct field access from x:Name var content = ModalContent?.Content; - // Try view-level implementation first - if ( - content is BalatroSeedOracle.Helpers.IModalBackNavigable viewNav - && viewNav.TryGoBack() - ) - { + if (content is IModalBackNavigable viewNav && viewNav.TryGoBack()) return true; - } - // Then try DataContext-level implementation (common for MVVM ViewModels) - if ( - content is Control control - && control.DataContext is BalatroSeedOracle.Helpers.IModalBackNavigable vmNav - ) + if (content is Control control + && control.DataContext is IModalBackNavigable vmNav + && vmNav.TryGoBack()) { - if (vmNav.TryGoBack()) - return true; + return true; } } catch (Exception ex) @@ -273,56 +144,5 @@ content is Control control return false; } - - /// - /// Shows a modal dialog with the specified content - /// - /// The parent window - /// The modal title - /// The content to display - /// Whether to show the back button - public static async Task ShowModal( - Window parent, - string title, - Control content, - bool showBackButton = true - ) - { - var modal = new StandardModal(); - modal.SetTitle(title); - modal.SetContent(content); - - // Direct field access from x:Name - if (modal.BackButton != null) - { - modal.BackButton.IsVisible = showBackButton; - } - - // Create overlay and show - var overlay = new Grid(); - overlay.Children.Add(modal); - - var mainGrid = parent.Content as Grid; - if (mainGrid != null) - { - mainGrid.Children.Add(overlay); - - // Handle back button click - modal.BackClicked += (s, e) => - { - mainGrid.Children.Remove(overlay); - }; - - // Create task completion source to await modal close - var tcs = new TaskCompletionSource(); - - modal.BackClicked += (s, e) => - { - tcs.SetResult(true); - }; - - await tcs.Task; - } - } } } From 4d219f79eb49818bad020178d778dc13f0aae876 Mon Sep 17 00:00:00 2001 From: "Nathanial P. Howard" Date: Sun, 10 May 2026 05:47:44 -0500 Subject: [PATCH 29/32] refactor(StandardModal): declarative slide-in animation and squeeze sizing in XAML Pairs with the code-behind refactor. Adds two style blocks: 1. Grid.modal-size with default 1080x600 and a UserControl.squeeze override that switches to NaN/NaN/MaxWidth=700/MaxHeight=500. The Squeeze styled property is bound to Classes.squeeze on the root UserControl via RelativeSource Self, replacing the imperative ApplySqueezeSizing() method. 2. Border.modal-border with a Style.Animations one-shot keyframe animation (Opacity 0->1, Margin 0,-24,0,24 -> 0) that runs once on first style match (i.e. on Loaded), replacing the imperative AnimateIn() method and Dispatcher.UIThread.Post tick. This is the Avalonia-idiomatic pattern for intro animations independent of property change. --- .../Views/Modals/StandardModal.axaml | 97 +++++++++++++------ 1 file changed, 67 insertions(+), 30 deletions(-) diff --git a/src/BalatroSeedOracle/Views/Modals/StandardModal.axaml b/src/BalatroSeedOracle/Views/Modals/StandardModal.axaml index ba45d3c..9b030f7 100644 --- a/src/BalatroSeedOracle/Views/Modals/StandardModal.axaml +++ b/src/BalatroSeedOracle/Views/Modals/StandardModal.axaml @@ -1,7 +1,43 @@ - + x:Class="BalatroSeedOracle.Views.Modals.StandardModal" + Classes.squeeze="{Binding Squeeze, RelativeSource={RelativeSource Mode=Self}}"> + + + + + + + + + + + @@ -17,21 +53,22 @@ - - + + + VerticalAlignment="Center"> - + - - - - - + + + + + - - + + - -