Skip to content

[WIP] UI improvements#235

Merged
patchzyy merged 23 commits intodevfrom
UI-improvements
Feb 25, 2026
Merged

[WIP] UI improvements#235
patchzyy merged 23 commits intodevfrom
UI-improvements

Conversation

@WantToBeeMe
Copy link
Member

@WantToBeeMe WantToBeeMe commented Feb 22, 2026

Important

This branch is based on the rewrite settings branch. since i was not so excited for the potential merge-conflicts that it could cause if i didn't.
Therefore, this branch can not be merged so long the settings rewrite is not merged

Before

image

After

image

New

added a new warning state to the behaviorTextBox. also imidiatly implemented it in the friends page
image

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Kitchen Sink page showcasing UI components and variants
    • Introduced tooltip bubble animations with improved visual presentation
    • Added warning message support in text input fields with visual indicators
    • Implemented duplicate friend detection with user-friendly alerts
  • UI/UX Improvements

    • Enhanced page transition animations when navigating between rooms
    • Improved keyboard focus behavior on click interactions
    • Refined component organization with updated visual styling
    • Added light/dark background toggle for UI preview mode
  • Bug Fixes

    • Fixed mod priority handling to prevent unnecessary save operations
    • Corrected mod enable/disable state synchronization across the interface
    • Resolved batch update issues with toggle-all functionality

@WantToBeeMe WantToBeeMe changed the title UI improvements [WIP] UI improvements Feb 22, 2026
@WantToBeeMe WantToBeeMe changed the base branch from dev to task/settings-rewrite-in-to-DI February 22, 2026 10:16
@coderabbitai
Copy link

coderabbitai bot commented Feb 22, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch UI-improvements

Comment @coderabbitai help to get the list of available commands and usage tips.

Base automatically changed from task/settings-rewrite-in-to-DI to dev February 23, 2026 14:07
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 33

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (12)
WheelWizard/Views/Patterns/MiiImages/BaseMiiImage.cs (2)

85-85: ⚠️ Potential issue | 🟠 Major

async void ReloadImages swallows exceptions.

Any exception thrown after an await inside ReloadImages (e.g., from MiiImageService.GetImageAsync) will be posted to the synchronization context unhandled, potentially crashing the app silently. Consider converting to async Task and surfacing errors through a state property (similar to how VrHistoryGraph exposes ErrorMessage), or at minimum wrap the body in a try/catch.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@WheelWizard/Views/Patterns/MiiImages/BaseMiiImage.cs` at line 85, The method
ReloadImages is declared async void which swallows exceptions; change its
signature to async Task (protected async Task ReloadImages(...)) and either wrap
the existing body in a try/catch to capture any exceptions from awaited calls
(e.g., MiiImageService.GetImageAsync) and set a view-model/state error property
(similar to VrHistoryGraph's ErrorMessage), or rethrow/log the exception so
callers can observe it; ensure callers that invoked ReloadImages are updated to
await the Task or explicitly fire-and-forget with proper exception handling.

83-90: ⚠️ Potential issue | 🟠 Major

_reloadCts is never disposed when the control is destroyed — resource leak.

ReloadImages disposes the previous CTS on each call, but the active CancellationTokenSource is never disposed when the control is detached from the visual tree. Add OnDetachedFromVisualTree to cancel and dispose:

🛠️ Proposed fix
+    protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+    {
+        base.OnDetachedFromVisualTree(e);
+        _reloadCts?.Cancel();
+        _reloadCts?.Dispose();
+        _reloadCts = null;
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@WheelWizard/Views/Patterns/MiiImages/BaseMiiImage.cs` around lines 83 - 90,
The active CancellationTokenSource stored in _reloadCts is never disposed when
the control is removed, causing a resource leak; add an override of
OnDetachedFromVisualTree (or the appropriate detach lifecycle method) in the
class that cancels and disposes _reloadCts (i.e., call _reloadCts?.Cancel();
_reloadCts?.Dispose(); _reloadCts = null) to mirror the cleanup done in
ReloadImages, ensuring you reference the existing _reloadCts field and keep
ReloadImages unchanged.
WheelWizard/Views/Patterns/VrHistoryGraph.axaml.cs (2)

515-519: 🧹 Nitpick | 🔵 Trivial

Inconsistent string slicing — prefer a single idiom.

digits[..4] uses range syntax while digits.Substring(4, 4) and digits.Substring(8, 4) use the older API in the same expression.

♻️ Proposed fix
-    return $"{digits[..4]}-{digits.Substring(4, 4)}-{digits.Substring(8, 4)}";
+    return $"{digits[..4]}-{digits[4..8]}-{digits[8..]}";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@WheelWizard/Views/Patterns/VrHistoryGraph.axaml.cs` around lines 515 - 519,
The code builds a formatted string from the local variable digits using mixed
slicing styles; make the slicing consistent by replacing the Substring calls
with range syntax (or vice versa) so all three segments use the same idiom
(e.g., digits[..4], digits[4..8], digits[8..12]) in the return expression that
formats the 12-digit value; update the expression that currently returns
$"{digits[..4]}-{digits.Substring(4, 4)}-{digits.Substring(8, 4)}" to use the
chosen consistent slicing approach.

17-17: 🧹 Nitpick | 🔵 Trivial

lifetimeDays constant should use PascalCase.

C# convention requires constants to be PascalCase (LifetimeDays). The field is referenced on lines 289 and 391.

♻️ Proposed fix
-    private const int lifetimeDays = 999;
+    private const int LifetimeDays = 999;

Also update references:

-    HistoryDaysDropdown.Items.Add(new HistoryDaysOption(999, "Lifetime")); // i think this option could make sense
+    HistoryDaysDropdown.Items.Add(new HistoryDaysOption(LifetimeDays, "Lifetime"));
-    DateRangeText = days == lifetimeDays ? $"Lifetime history" : $"Past {days} days ({fromDate} to {toDate})";
+    DateRangeText = days == LifetimeDays ? "Lifetime history" : $"Past {days} days ({fromDate} to {toDate})";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@WheelWizard/Views/Patterns/VrHistoryGraph.axaml.cs` at line 17, Rename the
constant lifetimeDays to PascalCase LifetimeDays in the VrHistoryGraph class and
update all references to use the new name (e.g., where lifetimeDays is read/used
around the graph rendering and lifetime calculations). Change the declaration of
the constant and replace every usage (including the references currently at the
two spots mentioned) to LifetimeDays so the code follows C# naming conventions
and compiles without errors.
WheelWizard/Views/Patterns/MiiImages/MiiImageLoader.axaml.cs (1)

14-16: ⚠️ Potential issue | 🟠 Major

Wrong owner type MiiCarousel in LowQualitySpeedupProperty registration.

The generic type argument for AvaloniaProperty.Register<TOwner, TValue> on Line 14 should be MiiImageLoader, not MiiCarousel. Using the wrong owner type can cause incorrect behavior in Avalonia's property inheritance and style-matching machinery.

🐛 Proposed fix
-    public static readonly StyledProperty<bool> LowQualitySpeedupProperty = AvaloniaProperty.Register<MiiCarousel, bool>(
+    public static readonly StyledProperty<bool> LowQualitySpeedupProperty = AvaloniaProperty.Register<MiiImageLoader, bool>(
         nameof(LowQualitySpeedup)
     );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@WheelWizard/Views/Patterns/MiiImages/MiiImageLoader.axaml.cs` around lines 14
- 16, The LowQualitySpeedupProperty is registered with the wrong owner type
(MiiCarousel); update the AvaloniaProperty registration to use MiiImageLoader as
the owner so the property is owned by the correct control. Locate the static
field LowQualitySpeedupProperty and change the generic owner type in the
AvaloniaProperty.Register<...> call from MiiCarousel to MiiImageLoader, ensuring
the nameof(LowQualitySpeedup) and the property type (bool) remain unchanged.
WheelWizard/Views/Popups/Generic/TextInputWindow.axaml (1)

22-24: ⚠️ Potential issue | 🟡 Minor

Remove hardcoded debug ErrorMessage.

ErrorMessage="hey hello" is a leftover placeholder that will unconditionally display as an error on this input field. It should either be empty or bound/set dynamically via code-behind.

🐛 Proposed fix
-<behaviorComp:FeedbackTextBox x:Name="InputField" Watermark="{x:Static lang:Phrases.Placeholder_EnterTextHere}"
-                              ErrorMessage="hey hello"
-                              Margin="0,5,0,0" Variant="Default" />
+<behaviorComp:FeedbackTextBox x:Name="InputField" Watermark="{x:Static lang:Phrases.Placeholder_EnterTextHere}"
+                              Margin="0,5,0,0" Variant="Default" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@WheelWizard/Views/Popups/Generic/TextInputWindow.axaml` around lines 22 - 24,
The FeedbackTextBox control named InputField contains a leftover hardcoded
ErrorMessage="hey hello" that causes an unconditional error state; remove the
literal ErrorMessage attribute (or set it to an empty string) and instead bind
ErrorMessage to a viewmodel property or set it dynamically in the
TextInputWindow code-behind (use InputField.ErrorMessage) so errors are shown
only when appropriate.
WheelWizard/Views/Patterns/ModBrowserListItem.axaml.cs (1)

70-93: ⚠️ Potential issue | 🟠 Major

new HttpClient() per template application is a socket-exhaustion risk.

OnApplyTemplate can be called for each item in a list. Instantiating a new HttpClient every time (and disposing it immediately via using) prevents connection reuse and can exhaust the socket pool under load.

Additionally, no Timeout is set, so a stalled server will hold the request open until the OS-level timeout (~2 min by default).

Prefer a shared/static HttpClient or inject IHttpClientFactory from the DI container, and set an explicit timeout:

🔧 Suggested approach
+private static readonly HttpClient _sharedClient = new() { Timeout = TimeSpan.FromSeconds(10) };
+
 protected override async void OnApplyTemplate(TemplateAppliedEventArgs e)
 {
     base.OnApplyTemplate(e);
     var image = e.NameScope.Find<Image>("ThumbnailImage");
     if (image == null || string.IsNullOrWhiteSpace(ImageUrl))
         return;

     try
     {
-        using var httpClient = new HttpClient();
-        var response = await httpClient.GetAsync(ImageUrl);
+        var response = await _sharedClient.GetAsync(ImageUrl);
         response.EnsureSuccessStatusCode();
         // ...
     }
     catch
     {
         // Ignore — no image is acceptable
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@WheelWizard/Views/Patterns/ModBrowserListItem.axaml.cs` around lines 70 - 93,
OnApplyTemplate creates a new HttpClient per call which risks socket exhaustion
and has no timeout; change the image-loading code in OnApplyTemplate (the
ThumbnailImage/ImageUrl handling) to use a reusable client: either obtain an
IHttpClientFactory from DI and call CreateClient(...) or use a shared static
HttpClient instance shared by the class, and set an explicit Timeout on that
client before calling GetAsync; keep the rest of the fetch/stream logic but
remove the per-call "new HttpClient()" usage and ensure proper error handling
when using the shared/injected client.
WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorGeneral.axaml.cs (1)

87-103: ⚠️ Potential issue | 🟠 Major

Fix the unsafe null-check in UserProfilePage.axaml.cs — it will throw NullReferenceException.

The ?? string.Empty guard in EditorGeneral.axaml.cs correctly prevents a NullReferenceException. However, UserProfilePage.axaml.cs lines 359–360 still uses the unsafe pattern:

newName = newName?.Trim();
if (newName.Length is > 10 or < 3)  // NRE if newName is null

When newName is null, newName?.Trim() returns null, then calling .Length throws NullReferenceException. Apply the same fix as EditorGeneral.axaml.cs:

var trimmedName = newName?.Trim() ?? string.Empty;
if (trimmedName.Length is > 10 or < 3)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorGeneral.axaml.cs`
around lines 87 - 103, The code in UserProfilePage.axaml.cs performs newName =
newName?.Trim(); then checks newName.Length which can throw when newName is
null; mirror the safe pattern used in EditorGeneral.axaml.cs (see
ValidateMiiName/ValidateCreatorName) by changing to var trimmedName =
newName?.Trim() ?? string.Empty; then use trimmedName.Length in the length check
and any subsequent logic instead of newName to avoid a NullReferenceException.
WheelWizard/Views/Patterns/MiiBlock.axaml.cs (1)

51-72: 🛠️ Refactor suggestion | 🟠 Major

OnApplyTemplate duplicates the body of SetupHoverVariant — call the helper instead.

Lines 61–64 in OnApplyTemplate are byte-for-byte identical to lines 79–82 in SetupHoverVariant. The helper exists precisely to avoid this; OnApplyTemplate should call it.

♻️ Proposed fix
     protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
     {
         base.OnApplyTemplate(e);
         _miiImageLoader = e.NameScope.Find<MiiImageLoaderWithHover>("PART_MiiImageLoader");
 
-        // Set hover variant immediately
         if (_miiImageLoader != null)
         {
-            // Create hover variant with smile expression
-            var hoverVariant = MiiImageVariants.MiiBlockProfile.Clone();
-            hoverVariant.Name = "MiiBlockProfileHover";
-            hoverVariant.Expression = MiiImageSpecifications.FaceExpression.smile;
-            _miiImageLoader.HoverVariant = hoverVariant;
-
-            // If Mii is already set, trigger reload
+            SetupHoverVariant();
             if (Mii != null)
             {
                 _miiImageLoader.ReloadBothVariants();
             }
         }
     }

Also applies to: 74-84

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@WheelWizard/Views/Patterns/MiiBlock.axaml.cs` around lines 51 - 72,
OnApplyTemplate currently contains a byte-for-byte duplicate of
SetupHoverVariant; simplify by keeping the _miiImageLoader lookup
(e.NameScope.Find<MiiImageLoaderWithHover>("PART_MiiImageLoader")) then call
SetupHoverVariant() instead of repeating the hover-variant creation and reload
logic. Ensure _miiImageLoader is assigned before calling SetupHoverVariant so
the helper can set HoverVariant and call ReloadBothVariants() when Mii != null,
and remove the duplicated block that creates hoverVariant and performs
ReloadBothVariants() in OnApplyTemplate.
WheelWizard/Views/Pages/ModsPage.axaml.cs (2)

85-91: 🧹 Nitpick | 🔵 Trivial

Cross-class nameof for PropertyChanged is fragile.

nameof(Mod.IsEnabled) is compared against a PropertyChangedEventArgs raised by ModManager. This relies on ModManager propagating the exact string "IsEnabled" — which currently works, but couples ModsPage to an undocumented contract in ModManager's implementation. If the property is moved, renamed, or the propagation changes, this breaks silently.

Consider using a shared constant or a strongly-typed event on ModManager specifically for enable-state changes instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@WheelWizard/Views/Pages/ModsPage.axaml.cs` around lines 85 - 91, The handler
OnModsChanged currently compares e.PropertyName to nameof(Mod.IsEnabled), which
couples ModsPage to Mod's implementation; change ModManager to expose a stable
identifier or event for enable-state changes (e.g., a public const string
EnabledPropertyName or a dedicated event like EnabledStateChanged) and update
OnModsChanged to check that constant or subscribe to the new strongly-typed
event instead of using nameof(Mod.IsEnabled); update references to
ModManager.Mods and UpdateEnableAllCheckboxState accordingly so the page reacts
to the stable contract rather than a cross-class nameof string.

67-83: ⚠️ Potential issue | 🟠 Major

Missing unsubscribe from ModManager.PropertyChanged — potential event handler leak.

ModManager.PropertyChanged += OnModsChanged is registered in the constructor but never removed (no Unloaded handler). If ModsPage is instantiated more than once during the app's lifetime (e.g., after navigating away and back), each instance will silently accumulate a live reference on the singleton ModManager, preventing GC of old page instances and causing duplicate UI updates.

Pattern already used on RoomsPage (Unloaded += RoomsPage_UnloadedRRLiveRooms.Instance.Unsubscribe) should be applied here.

🐛 Proposed fix
 public ModsPage()
 {
     InitializeComponent();
     DataContext = this;
     Focusable = true;
     ModManager.PropertyChanged += OnModsChanged;
     ModManager.ReloadAsync();
     SetModsViewVariant();

     // Apply priority edits as soon as the user clicks anywhere outside the textbox.
     AddHandler(PointerPressedEvent, OnPagePointerPressed, RoutingStrategies.Tunnel, true);

     // Wire up drag-and-drop pointer tracking
     PointerMoved += OnDragPointerMoved;
     PointerReleased += OnDragPointerReleased;
     PointerCaptureLost += OnDragPointerCaptureLost;
+
+    Unloaded += ModsPage_Unloaded;
 }

+private void ModsPage_Unloaded(object? sender, RoutedEventArgs e)
+{
+    ModManager.PropertyChanged -= OnModsChanged;
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@WheelWizard/Views/Pages/ModsPage.axaml.cs` around lines 67 - 83, ModsPage
registers ModManager.PropertyChanged (and several pointer handlers) in the
constructor but never unsubscribes them; add an Unloaded handler (e.g., Unloaded
+= ModsPage_Unloaded) and implement ModsPage_Unloaded to remove the subscription
ModManager.PropertyChanged -= OnModsChanged and detach the pointer handlers
added in the ctor (remove the AddHandler(PointerPressedEvent,
OnPagePointerPressed, ..., true) registration and unsubscribe PointerMoved -=
OnDragPointerMoved, PointerReleased -= OnDragPointerReleased, PointerCaptureLost
-= OnDragPointerCaptureLost) so instances can be GC'd and won't receive
duplicate events.
WheelWizard/Views/Patterns/FeedbackTextBox.axaml.cs (1)

96-103: 🧹 Nitpick | 🔵 Trivial

Constructor-time UpdateValidationState call is always a no-op.

At construction time, ErrorMessage and WarningMessage are both null (styled properties without defaults return null before XAML bindings are applied). Both flags evaluate to false, so this call unconditionally enters the !hasWarning branch and exits without modifying any classes. The real initialization is already handled by OnPropertyChanged once XAML property setters run.

Consider removing the call from the constructor to avoid confusion, or add a comment explaining why it's there.

🧹 Proposed cleanup
 public FeedbackTextBox()
 {
     InitializeComponent();
     DataContext = this;

     InputField.TextChanged += (_, _) => RaiseEvent(new TextChangedEventArgs(TextChangedEvent, this));
-    UpdateValidationState(hasError: !string.IsNullOrWhiteSpace(ErrorMessage), hasWarning: !string.IsNullOrWhiteSpace(WarningMessage));
     // If there is uses for more other events, then we can always add them
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@WheelWizard/Views/Patterns/FeedbackTextBox.axaml.cs` around lines 96 - 103,
The constructor in FeedbackTextBox calls UpdateValidationState with flags
derived from styled properties ErrorMessage and WarningMessage which are null at
construction, making that call a no-op; remove the UpdateValidationState(...)
call from the FeedbackTextBox() constructor (or replace it with a brief comment)
and rely on the existing OnPropertyChanged handling to initialize validation
state, ensuring you keep the InputField.TextChanged hookup and do not change
UpdateValidationState, ErrorMessage, WarningMessage, or OnPropertyChanged
implementations.
♻️ Duplicate comments (2)
WheelWizard/Views/Pages/kitchenSink/KitchenSinkTextStylesPage.axaml.cs (1)

6-6: Redundant SectionTooltip => null override — same as base default.

Same pattern as in KitchenSinkDropdownsPage and other section pages. See that file for the proposed fix.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@WheelWizard/Views/Pages/kitchenSink/KitchenSinkTextStylesPage.axaml.cs` at
line 6, Remove the redundant override of the SectionTooltip property in
KitchenSinkTextStylesPage: the override "public override string? SectionTooltip
=> null;" duplicates the base class default and should be deleted; locate the
SectionTooltip property in the KitchenSinkTextStylesPage class and remove that
override (matching the pattern used to clean up other pages such as
KitchenSinkDropdownsPage).
WheelWizard/Views/Pages/kitchenSink/KitchenSinkInputFieldsPage.axaml.cs (1)

6-6: Redundant SectionTooltip => null override — same as base default.

Same pattern as in KitchenSinkDropdownsPage. See that file for the proposed fix.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@WheelWizard/Views/Pages/kitchenSink/KitchenSinkInputFieldsPage.axaml.cs` at
line 6, Remove the redundant property override in KitchenSinkInputFieldsPage:
delete the SectionTooltip property override (public override string?
SectionTooltip => null;) since it matches the base default; mirror the change
made in KitchenSinkDropdownsPage so no behavior changes occur and ensure no
other code relies on this override.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@WheelWizard/Services/ModManager.cs`:
- Around line 116-125: The ModManager is raising PropertyChanged with Mod
property names (e.PropertyName like nameof(Mod.IsEnabled), nameof(Mod.Title),
etc.), which violates INotifyPropertyChanged for the ModManager; update the
ModManager's Mod.PropertyChanged handler so that after calling SaveModsAsync()
it calls OnPropertyChanged(nameof(Mods)) instead of
OnPropertyChanged(e.PropertyName), and make the same change in the other Mod
change handler referenced in the diff (leave SaveModsAsync and the
ModManager.Mods collection logic intact so subscribers like ModsPage still
receive updates for the Mods collection).

In `@WheelWizard/Views/App.axaml.cs`:
- Around line 47-48: The comment above ToolTipBubbleBehavior.Initialize() is
truncated; replace it with a complete sentence that explains what "Behavior
overrides" are and what this specific override does — e.g., state that behavior
overrides are native components used to change or extend control behavior at
runtime, and that ToolTipBubbleBehavior.Initialize() registers or configures the
tooltip behavior for the app. Update the comment text near the
ToolTipBubbleBehavior.Initialize() call to a full, self-contained description
referencing ToolTipBubbleBehavior.Initialize and the purpose of the override.

In `@WheelWizard/Views/Behaviors/ToolTipBubbleBehavior.cs`:
- Around line 55-56: ToolTip.SetServiceEnabled(control, false) disables the
platform tooltip service but the current ToolTipBubbleBehavior only opens/closes
tooltips on pointer events (the handlers around the pointer enter/leave code),
so keyboard-only focus won't show them; update ToolTipBubbleBehavior to also
subscribe to focus events (e.g., control.GotFocus/Focused and
control.LostFocus/Unfocused or corresponding FocusedChanged handlers depending
on control type) and call the same open/close logic used by the pointer handlers
(reuse the existing OpenToolTip/CloseToolTip methods or the same internal
show/hide code), and ensure you unsubscribe in the Detach/cleanup path so
behavior cleanup is symmetric with the pointer event handlers.
- Around line 125-132: OnPointerExited currently calls GetState(control) and
allocates state before checking whether the control actually has a tooltip open;
move the IsOpen check earlier to avoid creating state for controls that never
use tooltips: first call if (!ToolTip.GetIsOpen(control)) return; and only then
call var state = GetState(control) followed by CancelPendingOpen(state). Ensure
you reference the existing methods OnPointerExited, GetState and
CancelPendingOpen when making this change.

In `@WheelWizard/Views/Pages/FriendsPage.axaml`:
- Around line 8-9: Remove the duplicate xmlns alias by deleting the unused
xmlns:behaviorComponent declaration (which duplicates xmlns:patterns for
WheelWizard.Views.Patterns), then update any remaining usages of the old alias
(e.g., behaviorComponent:CurrentUserProfile) to use the migrated alias
(patterns:CurrentUserProfile) so only xmlns:patterns remains and no elements
reference behaviorComponent.

In `@WheelWizard/Views/Pages/FriendsPage.axaml.cs`:
- Around line 237-250: The duplicate-friend warning text in
ValidateFriendCodeWarning is hardcoded; replace the literal "This friend is
already in your list." with a localized resource lookup (e.g., use the app's
localization helper or resource manager) so the message comes from language
resources; update the return to fetch the translated string (referencing
ValidateFriendCodeWarning and the duplicateFriend branch) and add the new key to
the resource files used by the localization system, ensuring callers like
FriendCodeGenerator and GameLicenseService.ActiveCurrentFriends behavior remains
unchanged.

In `@WheelWizard/Views/Pages/HomePage.axaml`:
- Around line 179-182: Remove the three commented-out tooltip attribute lines
(ToolTip.Tip, ToolTip.Placement, ToolTip.ShowDelay) that follow the explanatory
comment in HomePage.axaml; keep the explanatory comment about removing the
tooltip and delete the dead code so the dolphin startup button markup no longer
contains those commented attributes.

In `@WheelWizard/Views/Pages/kitchenSink/KitchenSinkButtonsPage.axaml`:
- Line 74: Fix the typo in the TextBlock on KitchenSinkButtonsPage.axaml by
changing the Text attribute value from "Varients" to "Variants" (update the
TextBlock element with Classes="TinyText" Text="Varients" so it reads
Text="Variants").
- Around line 46-57: The "Text + Icon" demo button is rendering with the icon on
the left because the IsIconLeft property is not set; locate the
components:Button element with Text="Text + Icon" and IconData="{StaticResource
Download}" and set IsIconLeft="False" on that element so the icon appears on the
right (leaving the "Icon + Text" button unchanged).

In `@WheelWizard/Views/Pages/kitchenSink/KitchenSinkDropdownsPage.axaml.cs`:
- Line 6: Remove the redundant override of the SectionTooltip property in
KitchenSinkDropdownsPage (public override string? SectionTooltip => null;) since
the base class KitchenSinkSectionPageBase already returns null; locate and
delete the identical override in KitchenSinkDropdownsPage (and apply the same
removal to other pages like KitchenSinkTextStylesPage,
KitchenSinkInputFieldsPage, KitchenSinkButtonsPage) so the derived classes
inherit the base virtual SectionTooltip implementation instead of shadowing it
with a no-op override.

In `@WheelWizard/Views/Pages/kitchenSink/KitchenSinkIconLabelsPage.axaml.cs`:
- Line 6: Remove the redundant override of SectionTooltip in
KitchenSinkIconLabelsPage: delete the line "public override string?
SectionTooltip => null;" since KitchenSinkSectionPageBase already defines
"public virtual string? SectionTooltip => null;" and the override is a no-op;
ensure no other references depend on the override after removal.

In `@WheelWizard/Views/Pages/kitchenSink/KitchenSinkIconsPage.axaml.cs`:
- Line 6: Remove the redundant SectionTooltip override from KitchenSinkIconsPage
(and other Kitchen Sink pages that simply return null) since the base class
KitchenSinkSectionPageBase already defines public virtual string? SectionTooltip
=> null; — locate the override in the KitchenSinkIconsPage class and delete that
property override (and mirror the same removal in any peer pages) so they
inherit the base implementation instead of shadowing it.

In `@WheelWizard/Views/Pages/kitchenSink/KitchenSinkInputFieldsPage.axaml`:
- Line 5: Rename the XAML namespace alias from "behaviorComp" to "patterns" and
update the UI label text that currently reads "FeedbackTextBox Behavior
Component" to a name reflecting the new organization (e.g., "FeedbackTextBox
Pattern Component" or "FeedbackTextBox"); update the xmlns alias declaration
(the xmlns:behaviorComp attribute) and the Label/TextBlock content that uses
that alias in KitchenSinkInputFieldsPage.axaml so bindings/usages referencing
the alias and the visible label match the current Patterns namespace and naming.
- Around line 1-7: Rename the physical folder from "kitchenSink" to
"KitchenSink" so it matches the declared namespace
WheelWizard.Views.Pages.KitchenSink; update any project file entries or IDE
references if necessary and verify XAML class declarations such as
x:Class="WheelWizard.Views.Pages.KitchenSink.KitchenSinkInputFieldsPage" (and
the other eight KitchenSink pages) still resolve after the folder rename to
avoid case-sensitivity build issues on Linux/CI.

In `@WheelWizard/Views/Pages/kitchenSink/KitchenSinkSectionPageBase.cs`:
- Around line 3-7: The IKitchenSinkSection interface is unused outside
KitchenSinkSectionPageBase; either remove the interface or make generics use it:
if you don't need non-UserControlBase implementations, delete
IKitchenSinkSection and move its properties into KitchenSinkSectionPageBase
(remove references to IKitchenSinkSection); otherwise change KitchenSinkPage's
generic constraint from KitchenSinkSectionPageBase to IKitchenSinkSection so the
interface is actually leveraged and update any creation/usage sites to operate
against the interface rather than the concrete KitchenSinkSectionPageBase.

In `@WheelWizard/Views/Pages/KitchenSinkPage.axaml`:
- Around line 72-74: The XAML contains hard-coded user-facing strings (e.g., the
components:FormFieldLabel Text="Light Background" and TipText="Toggle section
block previews between light (Neutral900) and dark (Neutral950) backgrounds",
plus the strings on lines 79 and 84); move these into the app’s
localization/language resources and replace the literal attributes with resource
bindings (e.g., use StaticResource/x:Static or your IStringLocalizer/Loc
extension) so the FormFieldLabel.Text and TipText (and the other two literals)
reference resource keys instead of inline text; ensure resource keys are added
to the language resource file and referenced consistently in the KitchenSinkPage
(components:FormFieldLabel) XAML.

In `@WheelWizard/Views/Pages/KitchenSinkPage.axaml.cs`:
- Around line 204-212: SectionDefinition.Create<T> currently instantiates a
throwaway new T() (calling InitializeComponent) to read SectionName and
SectionTooltip; change this to avoid constructing the full page by making
SectionName and SectionTooltip static on KitchenSinkSectionPageBase subclasses
(or introduce a lightweight metadata type) and update
SectionDefinition.Create<T> to read those static members (or call a static
metadata accessor) and only use the Func<KitchenSinkSectionPageBase> CreatePage
to construct the instance when needed; update references to
SectionName/SectionTooltip in subclasses to the new static members or metadata
provider and ensure Create<T> returns new SectionDefinition(typeof(T).Name,
T.SectionName, T.SectionTooltip, () => new T()) (or equivalent using the
metadata accessor).
- Around line 70-90: CreateSectionContainer currently returns a Border and also
exposes the same instance via the out parameter sectionBorder, which is
redundant; remove the out parameter from the CreateSectionContainer signature,
have it simply return the Border, and update all callers (notably
BuildAllSections and ShowSingleSection) to stop expecting an out parameter and
to use the returned Border value directly (replace out variable usage with the
return). Ensure you remove any now-unused local variables that only existed to
receive the out value and adjust any downstream code that referenced that
variable to reference the returned Border instead.
- Around line 163-188: ShowSingleSection currently re-creates the page via
section.CreatePage() instead of using the cached container built by
BuildAllSections; change ShowSingleSection to look up the prebuilt container in
_allSectionContainersById by sectionId, retrieve the cached Border/container and
assign its border to _singleSectionBorder (instead of calling
CreateSectionContainer), set SectionContent.Content to a ScrollViewer wrapping
that cached container, and keep the existing visibility toggles and
ApplyBlockBackgroundMode call so the cached control tree is reused across
selections.

In `@WheelWizard/Views/Pages/MiiListPage.axaml`:
- Line 6: The xmlns alias xmlns:behaviorComponent is stale — rename the alias to
xmlns:patterns for the mapping to WheelWizard.Views.Patterns and update all XAML
references (any behaviorComponent:TypeName usages) in this file to
patterns:TypeName so the alias matches the namespace and is consistent with
other files like RoomDetailsPage/ModBrowserListItem; ensure you update any
element or attribute references that use the old behaviorComponent prefix to the
new patterns prefix.

In `@WheelWizard/Views/Pages/ModsPage.axaml.cs`:
- Around line 104-107: Replace the current check in UpdateEnableAllCheckboxState
that uses ModManager.Mods.Select(mod => mod.IsEnabled).Contains(false) with a
clearer All(...) predicate: set EnableAllCheckbox.IsChecked based on
ModManager.Mods.All(mod => mod.IsEnabled). Update the method
UpdateEnableAllCheckboxState to reference EnableAllCheckbox, ModManager.Mods and
the IsEnabled property accordingly so the intent is explicit.

In `@WheelWizard/Views/Patterns/CurrentUserProfile.axaml`:
- Line 3: Update the XML namespace alias in CurrentUserProfile.axaml from
behaviorComponent to patterns to reflect the actual CLR namespace
WheelWizard.Views.Patterns; change the xmlns declaration (currently
xmlns:behaviorComponent="clr-namespace:WheelWizard.Views.Patterns") to
xmlns:patterns="clr-namespace:WheelWizard.Views.Patterns" and update all usages
of the alias (every reference to behaviorComponent on lines where it's used) to
patterns so the alias name matches the namespace.

In `@WheelWizard/Views/Patterns/FeedbackTextBox.axaml`:
- Around line 7-9: The xmlns alias "behaviorComp" is misleading because it
points to WheelWizard.Views.Patterns; rename the XML namespace alias (e.g., to
"patterns" or "patternsNs") wherever declared and update the x:DataType and any
other references that use "behaviorComp" (such as
x:DataType="behaviorComp:FeedbackTextBox") to the new alias (for example
x:DataType="patterns:FeedbackTextBox") so the alias accurately reflects the
WheelWizard.Views.Patterns namespace and avoids confusion.

In `@WheelWizard/Views/Patterns/GridModPanel.axaml.cs`:
- Around line 15-16: The static ConcurrentDictionary s_imageCache currently
holds Bitmap instances for the process lifetime causing unbounded memory growth;
replace it with a bounded thread-safe cache (e.g., a fixed-capacity LRU or
size-limited cache used by GridModPanel) and ensure Bitmap instances are
disposed when evicted; update any places that read/write s_imageCache
(references in GridModPanel methods around the bitmap loading logic) to use the
new cache API so additions check capacity and evict/Dispose() the
least-recently-used Bitmap on overflow.
- Around line 78-85: The MemoryStream created for image decoding (memoryStream)
is not disposed; wrap its lifetime in a using/using-declaration around the copy
and Bitmap creation so the intermediate memory buffer is always disposed after
constructing the Bitmap (ensure you still call bitmap.Dispose() if
s_imageCache.TryAdd(modId, bitmap) returns false). Update the block that creates
memoryStream and the Bitmap (referencing memoryStream, Bitmap,
s_imageCache.TryAdd, modId, bitmap.Dispose) to ensure memoryStream is disposed
immediately after the Bitmap is instantiated.

In `@WheelWizard/Views/Patterns/LeaderboardPodiumCard.axaml`:
- Around line 4-5: Remove the duplicate xmlns alias by deleting the stale
declaration "behaviorComponent" (which points to WheelWizard.Views.Patterns) and
keep the single "patterns" xmlns; then replace any usages of
behaviorComponent:MiiImageLoader with patterns:MiiImageLoader so all references
use the consolidated patterns: alias (check the xmlns lines and the element on
line where behaviorComponent:MiiImageLoader is used).

In `@WheelWizard/Views/Patterns/MiiBlock.axaml`:
- Around line 4-6: Remove the duplicate xmlns mapping by deleting
xmlns:behaviorComponent="clr-namespace:WheelWizard.Views.Patterns" and keep a
single xmlns:patterns="clr-namespace:WheelWizard.Views.Patterns"; then replace
all usages of the old alias (currently referenced at the locations mentioned as
lines 48 and 90) to use patterns: for element/class references and patterns| for
any namespace-prefixed x:Name/markup usages so every reference formerly using
behaviorComponent now uses patterns.

In `@WheelWizard/Views/Patterns/MiiImages/MiiCarousel.axaml`:
- Line 6: Remove the unused XAML namespace alias "behaviorComp" which is
declared but never referenced; open MiiCarousel.axaml, find the
xmlns:behaviorComp declaration and delete that attribute so only the used
namespaces (e.g. components:) remain, ensuring references to AspectGrid and
Button (components:AspectGrid, components:Button) keep working and no other
xmlns attributes are accidentally modified.

In `@WheelWizard/Views/Patterns/MiiImages/MiiImageLoader.axaml`:
- Around line 7-10: The xmlns alias "behaviorComp" is now misleading (it points
to WheelWizard.Views.Patterns) so update the alias and its usage: rename the
xmlns:behaviorComp declaration to something like xmlns:patterns and then change
the x:DataType from behaviorComp:MiiImageLoader to patterns:MiiImageLoader
(ensure the x:Name="Self" remains unchanged); search for the xmlns:behaviorComp
and the x:DataType="behaviorComp:MiiImageLoader" occurrences and replace both to
keep the alias and MiiImageLoader binding consistent.

In `@WheelWizard/Views/Popups/Generic/TextInputWindow.axaml.cs`:
- Line 134: The Submit button is being disabled for warnings because
SubmitButton.IsEnabled is set to "!hasError && !hasWarning"; change the behavior
to make warnings non-blocking by removing "!hasWarning" from the enable guard
(leave SubmitButton.IsEnabled tied only to hasError) or, if you intend warnings
to block, rename or rework the API: either make SetWarningValidation ->
SetBlockingWarningValidation or move blocking validations into
inputValidationFunc (treat them as errors and surface the message via
WarningMessage styled as a warning). Update the logic that evaluates
warningValidationFunc/hasWarning and any documentation/comments to reflect the
chosen contract.

In `@WheelWizard/Views/Popups/MiiManagement/MiiEditorWindow.axaml`:
- Line 6: The xmlns alias "behavior" no longer matches the target namespace
WheelWizard.Views.Patterns; rename the alias to "patterns" in the xmlns
declaration and update all usages in this file (replace behavior:MiiImageLoader
and behavior:MiiCarousel with patterns:MiiImageLoader and patterns:MiiCarousel)
so the alias accurately reflects the namespace and avoids confusion.

In `@WheelWizard/Views/Styles/Styles/MiscStyles.axaml`:
- Around line 82-91: The tooltip tail is hardcoded for Top placement
(PART_ToolTipTail Path with Data="M 0 0 L 12 0 L 6 8 Z" in Grid.Row="1") and
will be wrong for Bottom/Left/Right; update the template so the tail adapts to
the control's Placement: either provide separate Path definitions/positions for
each placement and switch them via VisualStateManager or bind a RenderTransform
(rotate/flip and reposition) on PART_ToolTipTail to the Placement property
(TemplateBinding or a converter) so the Path Data/row aligns correctly for Top,
Bottom, Left, and Right. Ensure the element name PART_ToolTipTail remains and
that hit-testing and Stretch/Fill behavior are preserved.
- Around line 110-133: The Grid with name PART_ToolTipChrome used by the
ToolTip.BubbleAnimateIn and ToolTip.BubbleAnimateOut animations lacks a
RenderTransform, so TranslateTransform.Y animations are ignored; update the
template for Grid#PART_ToolTipChrome to include an explicit RenderTransform (a
TranslateTransform) so the KeyFrame setters (TranslateTransform.Y) in the
BubbleAnimateIn and BubbleAnimateOut styles can affect the control.

---

Outside diff comments:
In `@WheelWizard/Views/Pages/ModsPage.axaml.cs`:
- Around line 85-91: The handler OnModsChanged currently compares e.PropertyName
to nameof(Mod.IsEnabled), which couples ModsPage to Mod's implementation; change
ModManager to expose a stable identifier or event for enable-state changes
(e.g., a public const string EnabledPropertyName or a dedicated event like
EnabledStateChanged) and update OnModsChanged to check that constant or
subscribe to the new strongly-typed event instead of using
nameof(Mod.IsEnabled); update references to ModManager.Mods and
UpdateEnableAllCheckboxState accordingly so the page reacts to the stable
contract rather than a cross-class nameof string.
- Around line 67-83: ModsPage registers ModManager.PropertyChanged (and several
pointer handlers) in the constructor but never unsubscribes them; add an
Unloaded handler (e.g., Unloaded += ModsPage_Unloaded) and implement
ModsPage_Unloaded to remove the subscription ModManager.PropertyChanged -=
OnModsChanged and detach the pointer handlers added in the ctor (remove the
AddHandler(PointerPressedEvent, OnPagePointerPressed, ..., true) registration
and unsubscribe PointerMoved -= OnDragPointerMoved, PointerReleased -=
OnDragPointerReleased, PointerCaptureLost -= OnDragPointerCaptureLost) so
instances can be GC'd and won't receive duplicate events.

In `@WheelWizard/Views/Patterns/FeedbackTextBox.axaml.cs`:
- Around line 96-103: The constructor in FeedbackTextBox calls
UpdateValidationState with flags derived from styled properties ErrorMessage and
WarningMessage which are null at construction, making that call a no-op; remove
the UpdateValidationState(...) call from the FeedbackTextBox() constructor (or
replace it with a brief comment) and rely on the existing OnPropertyChanged
handling to initialize validation state, ensuring you keep the
InputField.TextChanged hookup and do not change UpdateValidationState,
ErrorMessage, WarningMessage, or OnPropertyChanged implementations.

In `@WheelWizard/Views/Patterns/MiiBlock.axaml.cs`:
- Around line 51-72: OnApplyTemplate currently contains a byte-for-byte
duplicate of SetupHoverVariant; simplify by keeping the _miiImageLoader lookup
(e.NameScope.Find<MiiImageLoaderWithHover>("PART_MiiImageLoader")) then call
SetupHoverVariant() instead of repeating the hover-variant creation and reload
logic. Ensure _miiImageLoader is assigned before calling SetupHoverVariant so
the helper can set HoverVariant and call ReloadBothVariants() when Mii != null,
and remove the duplicated block that creates hoverVariant and performs
ReloadBothVariants() in OnApplyTemplate.

In `@WheelWizard/Views/Patterns/MiiImages/BaseMiiImage.cs`:
- Line 85: The method ReloadImages is declared async void which swallows
exceptions; change its signature to async Task (protected async Task
ReloadImages(...)) and either wrap the existing body in a try/catch to capture
any exceptions from awaited calls (e.g., MiiImageService.GetImageAsync) and set
a view-model/state error property (similar to VrHistoryGraph's ErrorMessage), or
rethrow/log the exception so callers can observe it; ensure callers that invoked
ReloadImages are updated to await the Task or explicitly fire-and-forget with
proper exception handling.
- Around line 83-90: The active CancellationTokenSource stored in _reloadCts is
never disposed when the control is removed, causing a resource leak; add an
override of OnDetachedFromVisualTree (or the appropriate detach lifecycle
method) in the class that cancels and disposes _reloadCts (i.e., call
_reloadCts?.Cancel(); _reloadCts?.Dispose(); _reloadCts = null) to mirror the
cleanup done in ReloadImages, ensuring you reference the existing _reloadCts
field and keep ReloadImages unchanged.

In `@WheelWizard/Views/Patterns/MiiImages/MiiImageLoader.axaml.cs`:
- Around line 14-16: The LowQualitySpeedupProperty is registered with the wrong
owner type (MiiCarousel); update the AvaloniaProperty registration to use
MiiImageLoader as the owner so the property is owned by the correct control.
Locate the static field LowQualitySpeedupProperty and change the generic owner
type in the AvaloniaProperty.Register<...> call from MiiCarousel to
MiiImageLoader, ensuring the nameof(LowQualitySpeedup) and the property type
(bool) remain unchanged.

In `@WheelWizard/Views/Patterns/ModBrowserListItem.axaml.cs`:
- Around line 70-93: OnApplyTemplate creates a new HttpClient per call which
risks socket exhaustion and has no timeout; change the image-loading code in
OnApplyTemplate (the ThumbnailImage/ImageUrl handling) to use a reusable client:
either obtain an IHttpClientFactory from DI and call CreateClient(...) or use a
shared static HttpClient instance shared by the class, and set an explicit
Timeout on that client before calling GetAsync; keep the rest of the
fetch/stream logic but remove the per-call "new HttpClient()" usage and ensure
proper error handling when using the shared/injected client.

In `@WheelWizard/Views/Patterns/VrHistoryGraph.axaml.cs`:
- Around line 515-519: The code builds a formatted string from the local
variable digits using mixed slicing styles; make the slicing consistent by
replacing the Substring calls with range syntax (or vice versa) so all three
segments use the same idiom (e.g., digits[..4], digits[4..8], digits[8..12]) in
the return expression that formats the 12-digit value; update the expression
that currently returns $"{digits[..4]}-{digits.Substring(4,
4)}-{digits.Substring(8, 4)}" to use the chosen consistent slicing approach.
- Line 17: Rename the constant lifetimeDays to PascalCase LifetimeDays in the
VrHistoryGraph class and update all references to use the new name (e.g., where
lifetimeDays is read/used around the graph rendering and lifetime calculations).
Change the declaration of the constant and replace every usage (including the
references currently at the two spots mentioned) to LifetimeDays so the code
follows C# naming conventions and compiles without errors.

In `@WheelWizard/Views/Popups/Generic/TextInputWindow.axaml`:
- Around line 22-24: The FeedbackTextBox control named InputField contains a
leftover hardcoded ErrorMessage="hey hello" that causes an unconditional error
state; remove the literal ErrorMessage attribute (or set it to an empty string)
and instead bind ErrorMessage to a viewmodel property or set it dynamically in
the TextInputWindow code-behind (use InputField.ErrorMessage) so errors are
shown only when appropriate.

In `@WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorGeneral.axaml.cs`:
- Around line 87-103: The code in UserProfilePage.axaml.cs performs newName =
newName?.Trim(); then checks newName.Length which can throw when newName is
null; mirror the safe pattern used in EditorGeneral.axaml.cs (see
ValidateMiiName/ValidateCreatorName) by changing to var trimmedName =
newName?.Trim() ?? string.Empty; then use trimmedName.Length in the length check
and any subsequent logic instead of newName to avoid a NullReferenceException.

---

Duplicate comments:
In `@WheelWizard/Views/Pages/kitchenSink/KitchenSinkInputFieldsPage.axaml.cs`:
- Line 6: Remove the redundant property override in KitchenSinkInputFieldsPage:
delete the SectionTooltip property override (public override string?
SectionTooltip => null;) since it matches the base default; mirror the change
made in KitchenSinkDropdownsPage so no behavior changes occur and ensure no
other code relies on this override.

In `@WheelWizard/Views/Pages/kitchenSink/KitchenSinkTextStylesPage.axaml.cs`:
- Line 6: Remove the redundant override of the SectionTooltip property in
KitchenSinkTextStylesPage: the override "public override string? SectionTooltip
=> null;" duplicates the base class default and should be deleted; locate the
SectionTooltip property in the KitchenSinkTextStylesPage class and remove that
override (matching the pattern used to clean up other pages such as
KitchenSinkDropdownsPage).

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 631c828 and e62a8c7.

📒 Files selected for processing (110)
  • WheelWizard/Services/ModManager.cs
  • WheelWizard/Views/App.axaml
  • WheelWizard/Views/App.axaml.cs
  • WheelWizard/Views/Behaviors/ToolTipBubbleBehavior.cs
  • WheelWizard/Views/Components/AspectGrid.axaml
  • WheelWizard/Views/Components/AspectGrid.axaml.cs
  • WheelWizard/Views/Components/Badge.axaml
  • WheelWizard/Views/Components/Badge.axaml.cs
  • WheelWizard/Views/Components/Button.axaml
  • WheelWizard/Views/Components/Button.axaml.cs
  • WheelWizard/Views/Components/EmptyPageInfo.axaml
  • WheelWizard/Views/Components/EmptyPageInfo.axaml.cs
  • WheelWizard/Views/Components/FormFieldLabel.axaml
  • WheelWizard/Views/Components/FormFieldLabel.axaml.cs
  • WheelWizard/Views/Components/IconLabel.axaml
  • WheelWizard/Views/Components/IconLabel.axaml.cs
  • WheelWizard/Views/Components/IconLabelButton.axaml
  • WheelWizard/Views/Components/IconLabelButton.axaml.cs
  • WheelWizard/Views/Components/LoadingIcon.axaml
  • WheelWizard/Views/Components/LoadingIcon.axaml.cs
  • WheelWizard/Views/Components/MemeNumberState.axaml
  • WheelWizard/Views/Components/MemeNumberState.axaml.cs
  • WheelWizard/Views/Components/MultiColoredIcon.axaml
  • WheelWizard/Views/Components/MultiColoredIcon.axaml.cs
  • WheelWizard/Views/Components/MultiIconRadioButton.axaml
  • WheelWizard/Views/Components/MultiIconRadioButton.axaml.cs
  • WheelWizard/Views/Components/OptionButton.axaml
  • WheelWizard/Views/Components/OptionButton.axaml.cs
  • WheelWizard/Views/Components/PopupListButton.axaml
  • WheelWizard/Views/Components/PopupListButton.axaml.cs
  • WheelWizard/Views/Components/StandardLibrary/FormFieldLabel.axaml.cs
  • WheelWizard/Views/Components/StateBox.axaml
  • WheelWizard/Views/Components/StateBox.axaml.cs
  • WheelWizard/Views/Components/WheelTrail.axaml
  • WheelWizard/Views/Components/WheelTrail.axaml.cs
  • WheelWizard/Views/Layout.axaml
  • WheelWizard/Views/Layout.axaml.cs
  • WheelWizard/Views/Pages/FriendsPage.axaml
  • WheelWizard/Views/Pages/FriendsPage.axaml.cs
  • WheelWizard/Views/Pages/HomePage.axaml
  • WheelWizard/Views/Pages/HomePage.axaml.cs
  • WheelWizard/Views/Pages/KitchenSinkPage.axaml
  • WheelWizard/Views/Pages/KitchenSinkPage.axaml.cs
  • WheelWizard/Views/Pages/LeaderboardPage.axaml
  • WheelWizard/Views/Pages/MiiListPage.axaml
  • WheelWizard/Views/Pages/MiiListPage.axaml.cs
  • WheelWizard/Views/Pages/ModsPage.axaml
  • WheelWizard/Views/Pages/ModsPage.axaml.cs
  • WheelWizard/Views/Pages/RoomDetailsPage.axaml
  • WheelWizard/Views/Pages/RoomsPage.axaml
  • WheelWizard/Views/Pages/SettingsPage.axaml
  • WheelWizard/Views/Pages/SettingsPage.axaml.cs
  • WheelWizard/Views/Pages/UserProfilePage.axaml
  • WheelWizard/Views/Pages/kitchenSink/KitchenSinkButtonsPage.axaml
  • WheelWizard/Views/Pages/kitchenSink/KitchenSinkButtonsPage.axaml.cs
  • WheelWizard/Views/Pages/kitchenSink/KitchenSinkDropdownsPage.axaml
  • WheelWizard/Views/Pages/kitchenSink/KitchenSinkDropdownsPage.axaml.cs
  • WheelWizard/Views/Pages/kitchenSink/KitchenSinkIconLabelsPage.axaml
  • WheelWizard/Views/Pages/kitchenSink/KitchenSinkIconLabelsPage.axaml.cs
  • WheelWizard/Views/Pages/kitchenSink/KitchenSinkIconsPage.axaml
  • WheelWizard/Views/Pages/kitchenSink/KitchenSinkIconsPage.axaml.cs
  • WheelWizard/Views/Pages/kitchenSink/KitchenSinkInputFieldsPage.axaml
  • WheelWizard/Views/Pages/kitchenSink/KitchenSinkInputFieldsPage.axaml.cs
  • WheelWizard/Views/Pages/kitchenSink/KitchenSinkSectionPageBase.cs
  • WheelWizard/Views/Pages/kitchenSink/KitchenSinkStateBoxesPage.axaml
  • WheelWizard/Views/Pages/kitchenSink/KitchenSinkStateBoxesPage.axaml.cs
  • WheelWizard/Views/Pages/kitchenSink/KitchenSinkTextStylesPage.axaml
  • WheelWizard/Views/Pages/kitchenSink/KitchenSinkTextStylesPage.axaml.cs
  • WheelWizard/Views/Pages/kitchenSink/KitchenSinkToggleButtonsPage.axaml
  • WheelWizard/Views/Pages/kitchenSink/KitchenSinkToggleButtonsPage.axaml.cs
  • WheelWizard/Views/Patterns/CurrentUserProfile.axaml
  • WheelWizard/Views/Patterns/CurrentUserProfile.axaml.cs
  • WheelWizard/Views/Patterns/FeedbackTextBox.axaml
  • WheelWizard/Views/Patterns/FeedbackTextBox.axaml.cs
  • WheelWizard/Views/Patterns/FriendsListItem.axaml
  • WheelWizard/Views/Patterns/FriendsListItem.axaml.cs
  • WheelWizard/Views/Patterns/GridModPanel.axaml
  • WheelWizard/Views/Patterns/GridModPanel.axaml.cs
  • WheelWizard/Views/Patterns/LeaderboardPodiumCard.axaml
  • WheelWizard/Views/Patterns/LeaderboardPodiumCard.axaml.cs
  • WheelWizard/Views/Patterns/MiiBlock.axaml
  • WheelWizard/Views/Patterns/MiiBlock.axaml.cs
  • WheelWizard/Views/Patterns/MiiImages/BaseMiiImage.cs
  • WheelWizard/Views/Patterns/MiiImages/MiiCarousel.axaml
  • WheelWizard/Views/Patterns/MiiImages/MiiCarousel.axaml.cs
  • WheelWizard/Views/Patterns/MiiImages/MiiImageLoader.axaml
  • WheelWizard/Views/Patterns/MiiImages/MiiImageLoader.axaml.cs
  • WheelWizard/Views/Patterns/MiiImages/MiiImageLoaderWithHover.axaml
  • WheelWizard/Views/Patterns/MiiImages/MiiImageLoaderWithHover.axaml.cs
  • WheelWizard/Views/Patterns/ModBrowserListItem.axaml
  • WheelWizard/Views/Patterns/ModBrowserListItem.axaml.cs
  • WheelWizard/Views/Patterns/PlayerListItem.axaml
  • WheelWizard/Views/Patterns/PlayerListItem.axaml.cs
  • WheelWizard/Views/Patterns/SidebarRadioButton.axaml
  • WheelWizard/Views/Patterns/SidebarRadioButton.axaml.cs
  • WheelWizard/Views/Patterns/VrHistoryGraph.axaml
  • WheelWizard/Views/Patterns/VrHistoryGraph.axaml.cs
  • WheelWizard/Views/Popups/AddFriendConfirmationWindow.axaml
  • WheelWizard/Views/Popups/Generic/TextInputWindow.axaml
  • WheelWizard/Views/Popups/Generic/TextInputWindow.axaml.cs
  • WheelWizard/Views/Popups/MiiManagement/MiiCarouselWindow.axaml
  • WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorGeneral.axaml
  • WheelWizard/Views/Popups/MiiManagement/MiiEditor/EditorGeneral.axaml.cs
  • WheelWizard/Views/Popups/MiiManagement/MiiEditorWindow.axaml
  • WheelWizard/Views/Popups/MiiManagement/MiiSelectorWindow.axaml.cs
  • WheelWizard/Views/Popups/ModManagement/ModBrowserWindow.axaml
  • WheelWizard/Views/Popups/PlayerProfileWindow.axaml
  • WheelWizard/Views/Styles/Resources/Icons.axaml
  • WheelWizard/Views/Styles/Styles/MiscStyles.axaml
  • WheelWizard/Views/Styles/Styles/TextBoxStyles.axaml
💤 Files with no reviewable changes (2)
  • WheelWizard/Views/Components/StandardLibrary/FormFieldLabel.axaml.cs
  • WheelWizard/Views/Pages/HomePage.axaml.cs

Comment on lines +7 to 10
xmlns:behaviorComp="clr-namespace:WheelWizard.Views.Patterns"
xmlns:components="clr-namespace:WheelWizard.Views.Components"
xmlns:conv="clr-namespace:WheelWizard.Views.Converters"
x:DataType="behaviorComp:MiiImageLoader" x:Name="Self"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Stale alias name behaviorComp — rename to match its new target namespace.

The alias behaviorComp now maps to WheelWizard.Views.Patterns, but the name still implies BehaviorComponent. Since it's used exclusively for x:DataType on Line 10, renaming it (e.g., to patterns) removes the ambiguity introduced by the migration.

✏️ Proposed rename
-            xmlns:behaviorComp="clr-namespace:WheelWizard.Views.Patterns"
+            xmlns:patterns="clr-namespace:WheelWizard.Views.Patterns"
             xmlns:components="clr-namespace:WheelWizard.Views.Components"
             xmlns:conv="clr-namespace:WheelWizard.Views.Converters"
-            x:DataType="behaviorComp:MiiImageLoader" x:Name="Self"
+            x:DataType="patterns:MiiImageLoader" x:Name="Self"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
xmlns:behaviorComp="clr-namespace:WheelWizard.Views.Patterns"
xmlns:components="clr-namespace:WheelWizard.Views.Components"
xmlns:conv="clr-namespace:WheelWizard.Views.Converters"
x:DataType="behaviorComp:MiiImageLoader" x:Name="Self"
xmlns:patterns="clr-namespace:WheelWizard.Views.Patterns"
xmlns:components="clr-namespace:WheelWizard.Views.Components"
xmlns:conv="clr-namespace:WheelWizard.Views.Converters"
x:DataType="patterns:MiiImageLoader" x:Name="Self"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@WheelWizard/Views/Patterns/MiiImages/MiiImageLoader.axaml` around lines 7 -
10, The xmlns alias "behaviorComp" is now misleading (it points to
WheelWizard.Views.Patterns) so update the alias and its usage: rename the
xmlns:behaviorComp declaration to something like xmlns:patterns and then change
the x:DataType from behaviorComp:MiiImageLoader to patterns:MiiImageLoader
(ensure the x:Name="Self" remains unchanged); search for the xmlns:behaviorComp
and the x:DataType="behaviorComp:MiiImageLoader" occurrences and replace both to
keep the alias and MiiImageLoader binding consistent.


SubmitButton.IsEnabled = validationResultError == null;
InputField.ErrorMessage = validationResultError ?? "";
SubmitButton.IsEnabled = !hasError && !hasWarning;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Warnings disable the Submit button, making them semantically identical to errors.

!hasWarning in the IsEnabled expression means any warning produced by warningValidationFunc prevents submission. The conventional meaning of a "warning" is informational-but-non-blocking. Callers who invoke SetWarningValidation will reasonably expect the user can still submit — which is the pattern visible in the PR screenshot (yellow alert visible alongside an active Submit button).

If the intent is to block duplicate-friend submission on the friends page, consider either:

  • Keeping the block but renaming the API to reflect it (SetBlockingWarningValidation, or treating it as a second error channel), or
  • Removing !hasWarning from the IsEnabled guard so warnings display without blocking submission.
💡 Option A — non-blocking warning (user can submit despite warning)
-        SubmitButton.IsEnabled = !hasError && !hasWarning;
+        SubmitButton.IsEnabled = !hasError;
💡 Option B — keep blocking, but surface intent through the WarningMessage

If blocking is truly the desired behavior for the friends duplicate case, consider routing it through inputValidationFunc as an error with a "warning"-styled error message, or documenting the blocking contract on SetWarningValidation.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
SubmitButton.IsEnabled = !hasError && !hasWarning;
SubmitButton.IsEnabled = !hasError;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@WheelWizard/Views/Popups/Generic/TextInputWindow.axaml.cs` at line 134, The
Submit button is being disabled for warnings because SubmitButton.IsEnabled is
set to "!hasError && !hasWarning"; change the behavior to make warnings
non-blocking by removing "!hasWarning" from the enable guard (leave
SubmitButton.IsEnabled tied only to hasError) or, if you intend warnings to
block, rename or rework the API: either make SetWarningValidation ->
SetBlockingWarningValidation or move blocking validations into
inputValidationFunc (treat them as errors and surface the message via
WarningMessage styled as a warning). Update the logic that evaluates
warningValidationFunc/hasWarning and any documentation/comments to reflect the
chosen contract.

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:components="clr-namespace:WheelWizard.Views.Components"
xmlns:behavior="clr-namespace:WheelWizard.Views.BehaviorComponent"
xmlns:behavior="clr-namespace:WheelWizard.Views.Patterns"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider renaming the behavior alias to match the new namespace.

The alias xmlns:behavior now points to WheelWizard.Views.Patterns. The name behavior is a vestige of the old BehaviorComponent namespace and could mislead readers. Consider renaming it to patterns for consistency with the target namespace.

♻️ Suggested rename
-                   xmlns:behavior="clr-namespace:WheelWizard.Views.Patterns"
+                   xmlns:patterns="clr-namespace:WheelWizard.Views.Patterns"

Then update all usages in this file (behavior:MiiImageLoaderpatterns:MiiImageLoader, behavior:MiiCarouselpatterns:MiiCarousel).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@WheelWizard/Views/Popups/MiiManagement/MiiEditorWindow.axaml` at line 6, The
xmlns alias "behavior" no longer matches the target namespace
WheelWizard.Views.Patterns; rename the alias to "patterns" in the xmlns
declaration and update all usages in this file (replace behavior:MiiImageLoader
and behavior:MiiCarousel with patterns:MiiImageLoader and patterns:MiiCarousel)
so the alias accurately reflects the namespace and avoids confusion.

Comment on lines +110 to +133
<Style Selector="ToolTip.BubbleAnimateIn /template/ Grid#PART_ToolTipChrome">
<Style.Animations>
<Animation Duration="0:0:0.04" IterationCount="1" FillMode="Forward">
<KeyFrame Cue="0%">
<Setter Property="TranslateTransform.Y" Value="11"/>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="TranslateTransform.Y" Value="0"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="ToolTip.BubbleAnimateOut /template/ Grid#PART_ToolTipChrome">
<Style.Animations>
<Animation Duration="0:0:0.04" IterationCount="1" FillMode="Forward">
<KeyFrame Cue="0%">
<Setter Property="TranslateTransform.Y" Value="0"/>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="TranslateTransform.Y" Value="11"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

TranslateTransform.Y animation will silently not play — PART_ToolTipChrome has no RenderTransform set.

Avalonia requires RenderTransform to be set explicitly on a control for transform property animations to work; without it, the animation is silently skipped. The Grid#PART_ToolTipChrome in the template (lines 71–93) has no RenderTransform declared, so BubbleAnimateIn and BubbleAnimateOut will have no visible effect.

🐛 Proposed fix — add an explicit `TranslateTransform` to the template grid
     <Grid x:Name="PART_ToolTipChrome" RowDefinitions="Auto,Auto">
+        <Grid.RenderTransform>
+            <TranslateTransform />
+        </Grid.RenderTransform>
         <Border Grid.Row="0"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@WheelWizard/Views/Styles/Styles/MiscStyles.axaml` around lines 110 - 133, The
Grid with name PART_ToolTipChrome used by the ToolTip.BubbleAnimateIn and
ToolTip.BubbleAnimateOut animations lacks a RenderTransform, so
TranslateTransform.Y animations are ignored; update the template for
Grid#PART_ToolTipChrome to include an explicit RenderTransform (a
TranslateTransform) so the KeyFrame setters (TranslateTransform.Y) in the
BubbleAnimateIn and BubbleAnimateOut styles can affect the control.

@patchzyy patchzyy marked this pull request as ready for review February 25, 2026 17:53
@patchzyy patchzyy merged commit 9e26123 into dev Feb 25, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants