Skip to content

Avalonia migration#4214

Open
taooceros wants to merge 43 commits intodevfrom
avalonia_migration
Open

Avalonia migration#4214
taooceros wants to merge 43 commits intodevfrom
avalonia_migration

Conversation

@taooceros
Copy link
Member

@taooceros taooceros commented Jan 15, 2026

I actually vibe everything for this project (given the fact that it is mostly migration) as I want to see how far the agent can go. Very surprising, opencode can generate a working version with less than 3 hours (which I've been trying for much longer before).

Summary

This PR introduces an experimental Avalonia UI implementation alongside the existing WPF UI, allowing incremental migration and side-by-side comparison.

Architecture

  • New Flow.Launcher.Avalonia project targeting .NET 9 with Windows 10.0.19041
  • Shares core components (Flow.Launcher.Plugin, Flow.Launcher.Infrastructure, Flow.Launcher.Core) with WPF
  • Separate output directory (Output/Debug/Avalonia) to run independently from WPF version
  • Uses CommunityToolkit.Mvvm for MVVM pattern with source generators

Main Window & Query Flow

  • Basic main window with query textbox and results list
  • Full plugin system integration - queries route through existing PluginManager
  • Global hotkey support (Alt+Space) using native Windows hooks
  • Results sorted globally by score across all plugins
  • Progressive result display - results appear as each plugin completes without waiting for all

Results Handling

  • DynamicData SourceList for reactive collection management with automatic sorting
  • EditDiff for minimal UI updates when results change, reducing flickering
  • Results persist while typing until new results arrive
  • Keyboard navigation (Up/Down arrows, Enter to execute, Escape to hide)

Image Loading

  • Custom ImageLoader using Windows Shell API (IShellItemImageFactory)
  • Extracts icons from exe, dll, ico, lnk files and folders
  • Proper alpha channel handling with AlphaFormat.Unpremul for transparent icons
  • Async loading with caching

Performance

  • Plugin queries wrapped in Task.Run() to prevent UI blocking from synchronous plugin code
  • Parallel plugin execution with ConcurrentBag for thread-safe result accumulation
  • Background image loading to keep UI responsive

What Works

  • Launching and toggling window with global hotkey
  • Querying plugins and displaying results
  • Executing results (opening files, apps, etc.)
  • Icons for executables, folders, and standard image formats

Not Yet Implemented

  • Settings UI
  • Context menus
  • Themes/styling
  • System tray
  • Auto-updates
  • Many other WPF features

Create Flow.Launcher.Avalonia project as foundation for migrating from WPF to Avalonia UI framework.

Key components:
- MainWindow with query box and results list (matching WPF layout)
- ViewModels: MainViewModel, ResultsViewModel, ResultViewModel
- Themes/Base.axaml with converted styles from WPF
- FluentAvaloniaUI for Windows 11 styling
- References existing Core/Infrastructure/Plugin projects

The project builds and runs alongside the existing WPF application.
This is Phase 1 of the incremental migration approach.
- Load settings from disk via FlowLauncherJsonStorage
- Initialize PluginManager and query plugins on text change
- Add minimal AvaloniaPublicAPI implementing IPublicAPI
- Execute plugin results on Enter key
- Add WPF framework reference for IPublicAPI compatibility
…oading

- Use DynamicData SourceList with automatic sorting by score descending
- Add ReplaceResults() with EditDiff for minimal UI updates (reduces flickering)
- Keep previous results visible while typing until new results arrive
- Add ImageLoader with Windows Shell API (IShellItemImageFactory) for exe/ico icons
- Use AlphaFormat.Unpremul to correctly render transparent icons without white borders
- Query all plugins in parallel and merge/sort results globally
- Wrap each plugin query in Task.Run() to ensure synchronous plugin code
  doesn't block the UI thread
- Show results progressively as each plugin completes using ConcurrentBag
- Update UI after each plugin returns instead of waiting for all plugins
Copilot AI review requested due to automatic review settings January 15, 2026 08:31
@taooceros taooceros marked this pull request as draft January 15, 2026 08:31
@github-actions github-actions bot added this to the 2.1.0 milestone Jan 15, 2026
@github-actions

This comment has been minimized.

@taooceros taooceros modified the milestones: 2.1.0, Future, 3.0.0 Jan 15, 2026
@prlabeler prlabeler bot added bug Something isn't working enhancement New feature or request labels Jan 15, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a new Avalonia UI frontend for Flow Launcher as an alternative to the existing WPF implementation. The migration adds a complete new project with UI components, ViewModels, and platform integration while maintaining compatibility with the existing plugin system.

Changes:

  • Adds new Flow.Launcher.Avalonia project with complete UI implementation including MainWindow, result list views, and styling
  • Implements Windows global hotkey support and image loading utilities with native Win32 interop
  • Adds console logging output to infrastructure logger for easier debugging

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 50 comments.

Show a summary per file
File Description
Flow.Launcher.sln Adds Flow.Launcher.Avalonia project to solution with build configurations
Flow.Launcher.Infrastructure/Logger/Log.cs Adds console output for log messages
Flow.Launcher.Avalonia/app.manifest Defines Windows compatibility and DPI awareness settings
Flow.Launcher.Avalonia/Views/ResultListBox.axaml XAML markup for search results list display
Flow.Launcher.Avalonia/Views/ResultListBox.axaml.cs Code-behind for result list interactions
Flow.Launcher.Avalonia/ViewModel/ResultsViewModel.cs ViewModel managing result collection with sorting and selection
Flow.Launcher.Avalonia/ViewModel/ResultViewModel.cs ViewModel for individual result items
Flow.Launcher.Avalonia/ViewModel/MainViewModel.cs Main application ViewModel coordinating queries and UI state
Flow.Launcher.Avalonia/Themes/Resources.axaml Color and resource definitions for UI theme
Flow.Launcher.Avalonia/Themes/Base.axaml Style definitions for UI components
Flow.Launcher.Avalonia/Program.cs Application entry point
Flow.Launcher.Avalonia/MainWindow.axaml Main window XAML markup
Flow.Launcher.Avalonia/MainWindow.axaml.cs Main window code-behind with event handling
Flow.Launcher.Avalonia/Helper/ImageLoader.cs Image loading utility with caching and Win32 shell integration
Flow.Launcher.Avalonia/Helper/HotKeyMapper.cs Global hotkey registration management
Flow.Launcher.Avalonia/Helper/GlobalHotkey.cs Win32-based global hotkey implementation
Flow.Launcher.Avalonia/Flow.Launcher.Avalonia.csproj Project configuration and dependencies
Flow.Launcher.Avalonia/Converters/CommonConverters.cs Value converters for data binding
Flow.Launcher.Avalonia/Converters/BoolToIsVisibleConverter.cs Boolean to visibility converter
Flow.Launcher.Avalonia/AvaloniaPublicAPI.cs IPublicAPI implementation for plugin compatibility
Flow.Launcher.Avalonia/App.axaml Application XAML with theme configuration
Flow.Launcher.Avalonia/App.axaml.cs Application code-behind with initialization logic

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

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

Corrected spelling of 'true/pm' to 'true/PM' - the value appears to be malformed. It should be just 'true' for the dpiAware element, or 'PerMonitor' for per-monitor DPI awareness.

Suggested change
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>

Copilot uses AI. Check for mistakes.
Comment on lines +153 to +154
// Also output to console for easy debugging
System.Console.WriteLine($"[{level}] {classNameWithMethod}: {message}");
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

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

Console logging added to production code affects performance and should be conditional. Consider wrapping this in a DEBUG preprocessor directive or using a configuration flag to enable/disable console output, as this will log every message in Release builds.

Suggested change
// Also output to console for easy debugging
System.Console.WriteLine($"[{level}] {classNameWithMethod}: {message}");
// Also output to console for easy debugging
#if DEBUG
System.Console.WriteLine($"[{level}] {classNameWithMethod}: {message}");
#endif

Copilot uses AI. Check for mistakes.
Comment on lines +135 to +139
public void Dispose()
{
_subscription.Dispose();
_sourceList.Dispose();
}
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

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

The Dispose method doesn't follow the standard IDisposable pattern. Consider implementing the full pattern with a protected virtual Dispose(bool disposing) method and a finalizer to handle unmanaged resources properly, especially since this class manages disposable resources.

Copilot uses AI. Check for mistakes.

if (RegisterClass(ref wc) == 0)
{
Console.WriteLine("[GlobalHotkey] Failed to register window class");
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

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

Direct Console.WriteLine calls for logging should use the application's logging infrastructure (Flow.Launcher.Infrastructure.Logger.Log) for consistency and proper log management. This applies to all Console.WriteLine statements in this file.

Copilot uses AI. Check for mistakes.
var wc = new WNDCLASS
{
lpfnWndProc = _wndProc,
hInstance = GetModuleHandle(null),
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

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

Replace this call with a call to managed code if possible.

Suggested change
hInstance = GetModuleHandle(null),
hInstance = Marshal.GetHINSTANCE(typeof(GlobalHotkey).Module),

Copilot uses AI. Check for mistakes.
private string _subTitle = string.Empty;

[ObservableProperty]
private string _iconPath = string.Empty;
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

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

Field '_iconPath' can be 'readonly'.

Copilot uses AI. Check for mistakes.
public partial class ResultViewModel : ObservableObject
{
[ObservableProperty]
private string _title = string.Empty;
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

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

Field '_title' can be 'readonly'.

Copilot uses AI. Check for mistakes.

public partial class ResultListBox : UserControl
{
private ListBox? _listBox;
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

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

Field '_listBox' can be 'readonly'.

Copilot uses AI. Check for mistakes.
private int _selectedIndex;

[ObservableProperty]
private bool _isVisible = true;
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

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

Field '_isVisible' can be 'readonly'.

Copilot uses AI. Check for mistakes.
private bool _hasResults;

[ObservableProperty]
private ResultsViewModel _results;
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

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

Field '_results' can be 'readonly'.

Copilot uses AI. Check for mistakes.
- Add ActiveView enum to track Results vs ContextMenu view state
- Add ContextMenu ResultsViewModel and view switching logic
- Implement LoadContextMenuCommand using PluginManager.GetContextMenusForPlugin
- Add keyboard navigation: Shift+Enter/Right to open, Left/Escape to close
- Update SelectNextItem/SelectPrevItem to navigate appropriate list
- Update EscCommand to return from context menu before hiding
- Create Internationalization service that parses WPF XAML language files
- Load translations from main Languages/ folder and all plugin Languages/ folders
- Add LocalizeExtension markup extension and Translator helper for XAML/code
- Fix IPublicAPI.GetTranslation to use the i18n service for plugin context menus
- Update MainWindow to use localized placeholder text
@github-actions

This comment has been minimized.

- Internationalization.Initialize() called in constructor when DI creates it
- Remove InitializeInternationalization() method from App.axaml.cs
- Update AvaloniaPublicAPI and LocalizeExtension to use Ioc.Default.GetService
- Reorder ConfigureDI before i18n initialization
@github-actions

This comment has been minimized.

@TBM13
Copy link
Contributor

TBM13 commented Jan 16, 2026

Is there any actual benefit to switching to Avalonia or it's just for testing purposes?

@taooceros
Copy link
Member Author

Is there any actual benefit to switching to Avalonia or it's just for testing purposes?

There should be some performance improvement for animation. Besides, I also want to have the chance to make flow cross platform as I am gradually migrating my workflow to mac.

- MainWindowVisibility starts as false (window hidden)
- Window IsVisible bound to MainWindowVisibility
- Set MainWindowVisibility = true in OnPluginsReady() after plugins load
@github-actions

This comment has been minimized.

- Add Glyph and GlyphAvailable properties to ResultViewModel
- Set Glyph from plugin Result when creating result items
- Add resultGlyph style matching icon size (32x32)
- Update ResultListBox to show glyph or image icon based on ShowGlyph property
- ShowGlyph = true when UseGlyphIcons is enabled AND glyph is available
taooceros and others added 2 commits January 26, 2026 16:32
- Port ActionKeywordSetting window to Avalonia
- Includes keyword editing, enable/disable toggle, validation
- Handles Enter key to submit, Space key blocked in keyword input
…shes

- Remove WPF views (ActionKeywordSetting, ExplorerSettings, PreviewPanel, QuickAccessLinkSettings)
- Fix NullReferenceException in ActionKeywordModel by initializing settings
- Fix (false,false) crash case in EditActionKeywordAsync
- Add null-safe dialog patterns for owner window
- Move Focus() to OnOpened for reliable control attachment
- Add using for Avalonia PreviewPanel namespace in ResultManager
- Update Main.cs to use AvaloniaControl return type
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

38 issues found across 151 files

Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="Flow.Launcher.Avalonia/Views/Controls/HotkeyRecorderDialog.axaml">

<violation number="1" location="Flow.Launcher.Avalonia/Views/Controls/HotkeyRecorderDialog.axaml:33">
P3: Avoid hardcoding `FontSize` in XAML unless there is a specific design requirement. Prefer default styles or shared font-size resources so typography stays consistent with themes and accessibility settings.

(Based on your team's feedback about relying on default UI styles and font sizes.) [FEEDBACK_USED]</violation>
</file>

<file name="Flow.Launcher.Avalonia/Views/Controls/HotkeyControl.axaml">

<violation number="1" location="Flow.Launcher.Avalonia/Views/Controls/HotkeyControl.axaml:29">
P3: Avoid hardcoding font sizes in XAML; prefer the default/shared text styles or a resource-based style so fonts scale consistently across themes and accessibility settings.

(Based on your team's feedback about relying on default UI styles and font sizes.) [FEEDBACK_USED]</violation>
</file>

<file name="Flow.Launcher.Avalonia/Views/SettingPages/ThemeSettingsPage.axaml">

<violation number="1" location="Flow.Launcher.Avalonia/Views/SettingPages/ThemeSettingsPage.axaml:13">
P3: Avoid hardcoding font sizes in XAML; rely on default or shared styles so theme/accessibility updates apply consistently.

(Based on your team's feedback about relying on default UI styles and font sizes.) [FEEDBACK_USED]</violation>
</file>

<file name="Flow.Launcher.Avalonia/WpfResources/SettingWindowStyle.xaml">

<violation number="1" location="Flow.Launcher.Avalonia/WpfResources/SettingWindowStyle.xaml:116">
P3: Avoid hardcoding font sizes in XAML styles; use the default/shared typography resources so theme and accessibility changes can be applied globally.

(Based on your team's feedback about relying on default UI styles and font sizes.) [FEEDBACK_USED]</violation>
</file>

<file name="Flow.Launcher.Avalonia/ViewModel/ResultViewModel.cs">

<violation number="1" location="Flow.Launcher.Avalonia/ViewModel/ResultViewModel.cs:62">
P1: Image cache is not invalidated when `IconPath` changes. The `_imageTask` should be reset to `null` when `IconPath` is updated, otherwise the UI will continue displaying the old image. Implement the partial method `OnIconPathChanged` to clear the cache.</violation>
</file>

<file name="Flow.Launcher.Avalonia/Views/SettingPages/PluginStoreSettingsPage.axaml">

<violation number="1" location="Flow.Launcher.Avalonia/Views/SettingPages/PluginStoreSettingsPage.axaml:165">
P2: Hardcoded `Foreground="White"` won't adapt to theme changes. Use a theme-aware resource like `{DynamicResource TextOnAccentFillColorPrimaryBrush}` or define a contrast text brush resource to ensure visibility across light/dark themes.</violation>

<violation number="2" location="Flow.Launcher.Avalonia/Views/SettingPages/PluginStoreSettingsPage.axaml:169">
P2: Hardcoded `Foreground="Black"` won't adapt to theme changes. Use a theme-aware resource like `{DynamicResource TextOnAccentFillColorPrimaryBrush}` or an appropriate contrast text brush to ensure consistent appearance across themes.</violation>
</file>

<file name="Flow.Launcher.Avalonia/ViewModel/SettingPages/PluginStoreItemViewModel.cs">

<violation number="1" location="Flow.Launcher.Avalonia/ViewModel/SettingPages/PluginStoreItemViewModel.cs:75">
P2: Empty catch block silently swallows exceptions. Add logging for debugging purposes using `Log.Debug` to maintain consistency with the rest of the codebase (e.g., `ImageLoader.cs`).

(Based on your team's feedback about using consistent logging levels for debug information.) [FEEDBACK_USED]</violation>
</file>

<file name="Flow.Launcher.Avalonia/Views/Controls/HotkeyRecorderDialog.axaml.cs">

<violation number="1" location="Flow.Launcher.Avalonia/Views/Controls/HotkeyRecorderDialog.axaml.cs:70">
P2: Exception is silently swallowed with Debug.WriteLine. Use the project's logging infrastructure (`Log.Error`) for consistency and to ensure the error is properly recorded. Debug.WriteLine only outputs to debuggers and provides no indication to users if the hotkey hook fails to initialize.

(Based on your team's feedback about using consistent logging levels for debug information.) [FEEDBACK_USED]</violation>
</file>

<file name="Flow.Launcher.Avalonia/Helper/FontLoader.cs">

<violation number="1" location="Flow.Launcher.Avalonia/Helper/FontLoader.cs:74">
P2: Empty catch blocks suppress all exceptions, making debugging impossible. Consider adding debug-level logging to help diagnose font loading issues during development.

(Based on your team's feedback about using consistent logging levels for debug information.) [FEEDBACK_USED]</violation>
</file>

<file name="Flow.Launcher.Avalonia/Views/SettingPages/AboutSettingsPage.axaml">

<violation number="1" location="Flow.Launcher.Avalonia/Views/SettingPages/AboutSettingsPage.axaml:15">
P2: Avoid hardcoding FontSize in XAML; rely on default or shared styles so themes/accessibility can scale text consistently.

(Based on your team's feedback about relying on default UI styles and font sizes.) [FEEDBACK_USED]</violation>

<violation number="2" location="Flow.Launcher.Avalonia/Views/SettingPages/AboutSettingsPage.axaml:23">
P3: Avoid hardcoding FontSize in XAML; use shared styles or defaults so the UI can adapt to global typography changes.

(Based on your team's feedback about relying on default UI styles and font sizes.) [FEEDBACK_USED]</violation>
</file>

<file name="Flow.Launcher.Avalonia/Views/SettingPages/HotkeySettingsPage.axaml">

<violation number="1" location="Flow.Launcher.Avalonia/Views/SettingPages/HotkeySettingsPage.axaml:14">
P2: Avoid hardcoding FontSize in XAML; use the default/shared style so global theming and accessibility scaling stay consistent.

(Based on your team's feedback about relying on default UI styles and font sizes.) [FEEDBACK_USED]</violation>

<violation number="2" location="Flow.Launcher.Avalonia/Views/SettingPages/HotkeySettingsPage.axaml:24">
P3: Avoid hardcoding FontSize in XAML; use default/shared styles for consistent appearance across themes.

(Based on your team's feedback about relying on default UI styles and font sizes.) [FEEDBACK_USED]</violation>
</file>

<file name="Flow.Launcher.Avalonia/Themes/Base.axaml">

<violation number="1" location="Flow.Launcher.Avalonia/Themes/Base.axaml:30">
P3: Avoid hardcoded FontSize values in styles; prefer shared theme resources or default styles so font scaling and accessibility updates apply consistently.

(Based on your team's feedback about relying on default UI styles and font sizes.) [FEEDBACK_USED]</violation>
</file>

<file name="Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj">

<violation number="1" location="Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj:71">
P2: Adding Avalonia to the Plugin SDK forces all plugin developers to receive Avalonia as a transitive dependency, even if they only target WPF. Consider using conditional compilation or a separate package (e.g., `Flow.Launcher.Plugin.Avalonia`) for Avalonia-specific interfaces to keep the base SDK lightweight for developers who don't need Avalonia support.</violation>
</file>

<file name="Flow.Launcher.Avalonia/ViewModel/SettingPages/HotkeySettingsViewModel.cs">

<violation number="1" location="Flow.Launcher.Avalonia/ViewModel/SettingPages/HotkeySettingsViewModel.cs:25">
P2: Settings are updated before verifying the hotkey can be registered. If `HotKeyMapper.SetToggleHotkey()` fails (invalid hotkey string or registration conflict), `_settings.Hotkey` will contain a value that isn't actually active, causing user confusion. Consider calling `SetToggleHotkey` first or checking `HotKeyMapper.CheckAvailability()` before updating settings.</violation>
</file>

<file name="Flow.Launcher.Avalonia/ViewModel/MainViewModel.cs">

<violation number="1" location="Flow.Launcher.Avalonia/ViewModel/MainViewModel.cs:256">
P2: `CancellationTokenSource` implements `IDisposable` and should be disposed before creating a new one. This causes a resource leak on every keystroke. The WPF MainViewModel correctly disposes before reassigning.</violation>
</file>

<file name="Flow.Launcher.Avalonia/Views/SettingPages/ProxySettingsPage.axaml">

<violation number="1" location="Flow.Launcher.Avalonia/Views/SettingPages/ProxySettingsPage.axaml:13">
P3: Avoid hardcoding font sizes in XAML; rely on default/shared styles so theme and accessibility updates stay consistent.

(Based on your team's feedback about relying on default UI styles and font sizes.) [FEEDBACK_USED]</violation>
</file>

<file name="Flow.Launcher.Infrastructure/Logger/Log.cs">

<violation number="1" location="Flow.Launcher.Infrastructure/Logger/Log.cs:154">
P2: Unconditional Console.WriteLine in LogInternal will emit all logs to stdout in release builds, bypassing the configured NLog targets and potentially leaking or spamming output. Gate this to debug builds or remove in favor of a proper logging target.</violation>
</file>

<file name="Flow.Launcher.Avalonia/Views/Controls/ExCard.axaml">

<violation number="1" location="Flow.Launcher.Avalonia/Views/Controls/ExCard.axaml:38">
P3: Avoid hardcoding FontSize values in XAML; use the default/shared typography styles so theme and accessibility changes apply consistently across the UI.

(Based on your team's feedback about relying on default UI styles and font sizes.) [FEEDBACK_USED]</violation>
</file>

<file name="Flow.Launcher.Avalonia/Views/Controls/Card.axaml">

<violation number="1" location="Flow.Launcher.Avalonia/Views/Controls/Card.axaml:29">
P2: Hardcoding `FontSize="20"` bypasses shared typography resources and makes theme/accessibility changes harder. Prefer using the default TextBlock style or a shared resource for icon size instead of a literal value.

(Based on your team's feedback about relying on default UI styles and font sizes.) [FEEDBACK_USED]</violation>

<violation number="2" location="Flow.Launcher.Avalonia/Views/Controls/Card.axaml:47">
P2: Hardcoded `FontSize="12"` makes the subtitle ignore shared typography resources. Use the default style or a shared resource instead of a literal size to keep consistent theming.

(Based on your team's feedback about relying on default UI styles and font sizes.) [FEEDBACK_USED]</violation>
</file>

<file name="Flow.Launcher.Avalonia/Converters/CommonConverters.cs">

<violation number="1" location="Flow.Launcher.Avalonia/Converters/CommonConverters.cs:116">
P2: Use `CultureInfo.InvariantCulture` for parsing the ratio parameter. The current code uses culture-sensitive parsing, which will fail in locales that use comma as decimal separator (e.g., German, French). The WPF version correctly uses invariant culture.</violation>
</file>

<file name="Flow.Launcher.Avalonia/Helper/ImageLoader.cs">

<violation number="1" location="Flow.Launcher.Avalonia/Helper/ImageLoader.cs:26">
P2: Use the shared `Http` wrapper from `Flow.Launcher.Infrastructure.Http.Http` instead of creating a private HttpClient. The shared wrapper respects user-configured proxy settings and TLS configuration. You can use `Http.GetStreamAsync(url)` to download image data.

(Based on your team's feedback about using central Http wrapper to honor proxies.) [FEEDBACK_USED]</violation>
</file>

<file name="Flow.Launcher.Avalonia/ViewModel/SettingPages/PluginsSettingsViewModel.cs">

<violation number="1" location="Flow.Launcher.Avalonia/ViewModel/SettingPages/PluginsSettingsViewModel.cs:239">
P1: Memory leak: event subscription without unsubscription. The anonymous lambda captures `this` and subscribes to `_plugin.Metadata.PropertyChanged`, but the handler is never removed. Consider implementing `IDisposable` and storing the handler in a field so it can be unsubscribed in `Dispose()`, or use a weak event pattern.</violation>

<violation number="2" location="Flow.Launcher.Avalonia/ViewModel/SettingPages/PluginsSettingsViewModel.cs:247">
P2: Fire-and-forget async without error handling. If `LoadIconAsync` throws an exception, it will become an unobserved task exception. Add try-catch inside the method or use `ContinueWith` to handle errors.</violation>
</file>

<file name="Flow.Launcher.Avalonia/Resource/LocalizeExtension.cs">

<violation number="1" location="Flow.Launcher.Avalonia/Resource/LocalizeExtension.cs:50">
P2: Empty catch block swallows all exceptions silently. This is inconsistent with the logging pattern used in `Internationalization.cs`. Consider catching `InvalidOperationException` specifically (what IoC throws) or at minimum logging the exception.

(Based on your team's feedback about using consistent logging levels for debug information.) [FEEDBACK_USED]</violation>

<violation number="2" location="Flow.Launcher.Avalonia/Resource/LocalizeExtension.cs:102">
P2: `string.Format` failures are silently swallowed, which can hide bugs when format placeholders don't match arguments. Consider logging a warning with the key and exception details so developers can identify incorrect format strings.</violation>
</file>

<file name="Flow.Launcher.Avalonia/ViewModel/ResultsViewModel.cs">

<violation number="1" location="Flow.Launcher.Avalonia/ViewModel/ResultsViewModel.cs:154">
P1: The `ResultViewModelComparer` compares only `Title` and `SubTitle`, but not `Score`. When `EditDiff` finds matching items, it keeps the existing item with its old Score value. Since results are sorted by Score descending, this can cause incorrect ordering when a result's relevance score changes between queries.

Consider including `Score` in the comparison, or manually updating the Score of existing items when matches are found.</violation>
</file>

<file name="Flow.Launcher.Avalonia/AvaloniaPublicAPI.cs">

<violation number="1" location="Flow.Launcher.Avalonia/AvaloniaPublicAPI.cs:69">
P2: The `FileNameOrFilePath` parameter is ignored. When provided, the API should select/highlight that file in the opened directory. Consider using `/select,` argument with `explorer.exe` to implement proper file selection.</violation>

<violation number="2" location="Flow.Launcher.Avalonia/AvaloniaPublicAPI.cs:123">
P2: Inconsistent null handling - `_getMainViewModel()` is accessed with `.` here but with `?.` in other methods like `HideMainWindow`. This could cause NullReferenceException if the ViewModel is not yet initialized.</violation>
</file>

<file name="Flow.Launcher.Avalonia/ViewModel/SettingPages/PluginStoreSettingsViewModel.cs">

<violation number="1" location="Flow.Launcher.Avalonia/ViewModel/SettingPages/PluginStoreSettingsViewModel.cs:19">
P2: Fire-and-forget async call in constructor without exception handling. If `LoadPluginsAsync` throws, the exception will be unobserved. Consider wrapping the call body in try-catch and logging any exceptions, or using a helper method that handles unobserved exceptions.</violation>
</file>

<file name="Flow.Launcher.Avalonia/Views/SettingPages/GeneralSettingsPage.axaml">

<violation number="1" location="Flow.Launcher.Avalonia/Views/SettingPages/GeneralSettingsPage.axaml:17">
P2: Avoid hardcoding FontSize here; use the default style or a shared resource so theme/accessibility changes apply consistently.

(Based on your team's feedback about relying on default UI styles and font sizes.) [FEEDBACK_USED]</violation>
</file>

<file name="Flow.Launcher.Avalonia/Program.cs">

<violation number="1" location="Flow.Launcher.Avalonia/Program.cs:49">
P2: Use the project's `Log` utility instead of `System.Diagnostics.Debug.WriteLine` for exception logging. `Debug.WriteLine` is stripped in Release builds, so this error would be silently swallowed in production, making plugin compatibility issues very hard to diagnose.

(Based on your team's feedback about using consistent logging levels for debug information.) [FEEDBACK_USED]</violation>
</file>

<file name="Flow.Launcher.Avalonia/Views/Controls/HotkeyControl.axaml.cs">

<violation number="1" location="Flow.Launcher.Avalonia/Views/Controls/HotkeyControl.axaml.cs:76">
P1: Missing hotkey registration after save. The new hotkey value is stored in the property but never registered with the system. Add `HotKeyMapper.SetToggleHotkey(dialog.ResultValue)` after setting the property to ensure the hotkey actually works.</violation>
</file>

<file name="Flow.Launcher.Avalonia/Views/PreviewPanel.axaml">

<violation number="1" location="Flow.Launcher.Avalonia/Views/PreviewPanel.axaml:55">
P2: Hardcoded UI labels (“Score”, “Plugin”) should be localized via the i18n extension with keys in Languages/en.xaml so translations work across locales.</violation>
</file>

<file name="Flow.Launcher.Avalonia/Helper/GlobalHotkey.cs">

<violation number="1" location="Flow.Launcher.Avalonia/Helper/GlobalHotkey.cs:180">
P2: Use the established `Log` class from `Flow.Launcher.Infrastructure.Logger` instead of `Console.WriteLine` for logging. The project has proper logging infrastructure with level control. For debug/status messages use `Log.Debug(nameof(GlobalHotkey), "Initialized successfully")`, for errors use `Log.Error()` or `Log.Warn()`.

(Based on your team's feedback about using consistent logging levels for debug information.) [FEEDBACK_USED]</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

/// The icon image task. Use with Avalonia's ^ stream binding operator.
/// Returns a cached task to avoid re-loading on every property access.
/// </summary>
public Task<IImage?> Image => _imageTask ??= ImageLoader.LoadAsync(IconPath);
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 31, 2026

Choose a reason for hiding this comment

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

P1: Image cache is not invalidated when IconPath changes. The _imageTask should be reset to null when IconPath is updated, otherwise the UI will continue displaying the old image. Implement the partial method OnIconPathChanged to clear the cache.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Flow.Launcher.Avalonia/ViewModel/ResultViewModel.cs, line 62:

<comment>Image cache is not invalidated when `IconPath` changes. The `_imageTask` should be reset to `null` when `IconPath` is updated, otherwise the UI will continue displaying the old image. Implement the partial method `OnIconPathChanged` to clear the cache.</comment>

<file context>
@@ -0,0 +1,90 @@
+    /// The icon image task. Use with Avalonia's ^ stream binding operator.
+    /// Returns a cached task to avoid re-loading on every property access.
+    /// </summary>
+    public Task<IImage?> Image => _imageTask ??= ImageLoader.LoadAsync(IconPath);
+
+    // Glyph support
</file context>
Fix with Cubic

}

// Listen to metadata changes
_plugin.Metadata.PropertyChanged += (_, args) =>
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 31, 2026

Choose a reason for hiding this comment

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

P1: Memory leak: event subscription without unsubscription. The anonymous lambda captures this and subscribes to _plugin.Metadata.PropertyChanged, but the handler is never removed. Consider implementing IDisposable and storing the handler in a field so it can be unsubscribed in Dispose(), or use a weak event pattern.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Flow.Launcher.Avalonia/ViewModel/SettingPages/PluginsSettingsViewModel.cs, line 239:

<comment>Memory leak: event subscription without unsubscription. The anonymous lambda captures `this` and subscribes to `_plugin.Metadata.PropertyChanged`, but the handler is never removed. Consider implementing `IDisposable` and storing the handler in a field so it can be unsubscribed in `Dispose()`, or use a weak event pattern.</comment>

<file context>
@@ -0,0 +1,467 @@
+        }
+
+        // Listen to metadata changes
+        _plugin.Metadata.PropertyChanged += (_, args) =>
+        {
+            if (args.PropertyName == nameof(PluginMetadata.AvgQueryTime))
</file context>
Fix with Cubic


if (result == HotkeyRecorderDialog.EResultType.Save)
{
Hotkey = dialog.ResultValue;
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 31, 2026

Choose a reason for hiding this comment

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

P1: Missing hotkey registration after save. The new hotkey value is stored in the property but never registered with the system. Add HotKeyMapper.SetToggleHotkey(dialog.ResultValue) after setting the property to ensure the hotkey actually works.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Flow.Launcher.Avalonia/Views/Controls/HotkeyControl.axaml.cs, line 76:

<comment>Missing hotkey registration after save. The new hotkey value is stored in the property but never registered with the system. Add `HotKeyMapper.SetToggleHotkey(dialog.ResultValue)` after setting the property to ensure the hotkey actually works.</comment>

<file context>
@@ -0,0 +1,89 @@
+
+            if (result == HotkeyRecorderDialog.EResultType.Save)
+            {
+                Hotkey = dialog.ResultValue;
+            }
+            else if (result == HotkeyRecorderDialog.EResultType.Delete)
</file context>
Fix with Cubic

</Border>
<Border Background="{DynamicResource SystemFillColorAttentionBrush}" CornerRadius="4" Padding="6,2"
IsVisible="{Binding LabelUpdate}">
<TextBlock Text="{i18n:Localize pluginStore_NewRelease}" FontSize="10" Foreground="Black"/>
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 31, 2026

Choose a reason for hiding this comment

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

P2: Hardcoded Foreground="Black" won't adapt to theme changes. Use a theme-aware resource like {DynamicResource TextOnAccentFillColorPrimaryBrush} or an appropriate contrast text brush to ensure consistent appearance across themes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Flow.Launcher.Avalonia/Views/SettingPages/PluginStoreSettingsPage.axaml, line 169:

<comment>Hardcoded `Foreground="Black"` won't adapt to theme changes. Use a theme-aware resource like `{DynamicResource TextOnAccentFillColorPrimaryBrush}` or an appropriate contrast text brush to ensure consistent appearance across themes.</comment>

<file context>
@@ -0,0 +1,200 @@
+                                        </Border>
+                                        <Border Background="{DynamicResource SystemFillColorAttentionBrush}" CornerRadius="4" Padding="6,2"
+                                                IsVisible="{Binding LabelUpdate}">
+                                            <TextBlock Text="{i18n:Localize pluginStore_NewRelease}" FontSize="10" Foreground="Black"/>
+                                        </Border>
+                                    </StackPanel>
</file context>
Fix with Cubic

<Button Content="GitHub" Command="{Binding OpenGitHubCommand}" Width="120" />
</StackPanel>

<TextBlock Text="Copyright © 2024 Flow Launcher" Foreground="Gray" FontSize="12" HorizontalAlignment="Center" Margin="0,50,0,0" />
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 31, 2026

Choose a reason for hiding this comment

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

P3: Avoid hardcoding FontSize in XAML; use shared styles or defaults so the UI can adapt to global typography changes.

(Based on your team's feedback about relying on default UI styles and font sizes.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Flow.Launcher.Avalonia/Views/SettingPages/AboutSettingsPage.axaml, line 23:

<comment>Avoid hardcoding FontSize in XAML; use shared styles or defaults so the UI can adapt to global typography changes.

(Based on your team's feedback about relying on default UI styles and font sizes.) </comment>

<file context>
@@ -0,0 +1,25 @@
+            <Button Content="GitHub" Command="{Binding OpenGitHubCommand}" Width="120" />
+        </StackPanel>
+
+        <TextBlock Text="Copyright © 2024 Flow Launcher" Foreground="Gray" FontSize="12" HorizontalAlignment="Center" Margin="0,50,0,0" />
+    </StackPanel>
+</UserControl>
</file context>
Fix with Cubic

</ui:SettingsExpander.Footer>
</ui:SettingsExpander>

<TextBlock Text="{i18n:Localize hotkeyFormatTip}" Foreground="Gray" FontSize="12" />
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 31, 2026

Choose a reason for hiding this comment

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

P3: Avoid hardcoding FontSize in XAML; use default/shared styles for consistent appearance across themes.

(Based on your team's feedback about relying on default UI styles and font sizes.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Flow.Launcher.Avalonia/Views/SettingPages/HotkeySettingsPage.axaml, line 24:

<comment>Avoid hardcoding FontSize in XAML; use default/shared styles for consistent appearance across themes.

(Based on your team's feedback about relying on default UI styles and font sizes.) </comment>

<file context>
@@ -0,0 +1,26 @@
+            </ui:SettingsExpander.Footer>
+        </ui:SettingsExpander>
+        
+        <TextBlock Text="{i18n:Localize hotkeyFormatTip}" Foreground="Gray" FontSize="12" />
+    </StackPanel>
+</UserControl>
</file context>
Fix with Cubic

<!-- Query Text Box -->
<Style Selector="TextBox.queryBox">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="FontSize" Value="24" />
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 31, 2026

Choose a reason for hiding this comment

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

P3: Avoid hardcoded FontSize values in styles; prefer shared theme resources or default styles so font scaling and accessibility updates apply consistently.

(Based on your team's feedback about relying on default UI styles and font sizes.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Flow.Launcher.Avalonia/Themes/Base.axaml, line 30:

<comment>Avoid hardcoded FontSize values in styles; prefer shared theme resources or default styles so font scaling and accessibility updates apply consistently.

(Based on your team's feedback about relying on default UI styles and font sizes.) </comment>

<file context>
@@ -0,0 +1,216 @@
+    <!-- Query Text Box -->
+    <Style Selector="TextBox.queryBox">
+        <Setter Property="BorderThickness" Value="0" />
+        <Setter Property="FontSize" Value="24" />
+        <Setter Property="Margin" Value="16,7,60,7" />
+        <Setter Property="Padding" Value="0" />
</file context>
Fix with Cubic

x:DataType="vm:ProxySettingsViewModel">

<StackPanel Spacing="20">
<TextBlock Text="{i18n:Localize proxy}" FontSize="28" FontWeight="SemiBold" />
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 31, 2026

Choose a reason for hiding this comment

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

P3: Avoid hardcoding font sizes in XAML; rely on default/shared styles so theme and accessibility updates stay consistent.

(Based on your team's feedback about relying on default UI styles and font sizes.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Flow.Launcher.Avalonia/Views/SettingPages/ProxySettingsPage.axaml, line 13:

<comment>Avoid hardcoding font sizes in XAML; rely on default/shared styles so theme and accessibility updates stay consistent.

(Based on your team's feedback about relying on default UI styles and font sizes.) </comment>

<file context>
@@ -0,0 +1,47 @@
+             x:DataType="vm:ProxySettingsViewModel">
+
+    <StackPanel Spacing="20">
+        <TextBlock Text="{i18n:Localize proxy}" FontSize="28" FontWeight="SemiBold" />
+
+        <ui:SettingsExpander Header="{i18n:Localize proxyEnabled}"
</file context>
Fix with Cubic

<TextBlock Grid.Column="0"
Text="{TemplateBinding Icon}"
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="20"
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 31, 2026

Choose a reason for hiding this comment

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

P3: Avoid hardcoding FontSize values in XAML; use the default/shared typography styles so theme and accessibility changes apply consistently across the UI.

(Based on your team's feedback about relying on default UI styles and font sizes.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Flow.Launcher.Avalonia/Views/Controls/ExCard.axaml, line 38:

<comment>Avoid hardcoding FontSize values in XAML; use the default/shared typography styles so theme and accessibility changes apply consistently across the UI.

(Based on your team's feedback about relying on default UI styles and font sizes.) </comment>

<file context>
@@ -0,0 +1,78 @@
+                                <TextBlock Grid.Column="0"
+                                           Text="{TemplateBinding Icon}"
+                                           FontFamily="{StaticResource SymbolThemeFontFamily}"
+                                           FontSize="20"
+                                           Foreground="{DynamicResource TextFillColorPrimaryBrush}"
+                                           VerticalAlignment="Center"
</file context>
Fix with Cubic

Copy link
Contributor

@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: 16

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (4)
Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs (1)

604-612: ⚠️ Potential issue | 🟡 Minor

Copy-paste bug: wrong settings property used for initial directory.

OpenShellPathAsync uses Settings.EditorPath instead of Settings.ShellPath to determine the initial directory for the file picker.

🐛 Proposed fix
 [RelayCommand]
 private async Task OpenShellPathAsync()
 {
-    var path = await PromptUserSelectFileAsync(Settings.EditorPath != null ? Path.GetDirectoryName(Settings.EditorPath) : null);
+    var path = await PromptUserSelectFileAsync(Settings.ShellPath != null ? Path.GetDirectoryName(Settings.ShellPath) : null);
     if (path is null)
         return;

     ShellPath = path;
 }
Flow.Launcher/packages.lock.json (1)

1-1662: ⚠️ Potential issue | 🟠 Major

Resolve merge conflict before merging.

The pipeline indicates a merge conflict in this file that must be resolved.

Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj (1)

1-83: ⚠️ Potential issue | 🟠 Major

Resolve the merge conflict reported by CI.

The pipeline indicates a merge conflict in this file. Please re-check for conflict markers and re-save the file so CI can proceed.

Flow.Launcher.Core/packages.lock.json (1)

1-5: ⚠️ Potential issue | 🟠 Major

Resolve the merge conflict in this lockfile.

The pipeline reports a merge conflict in this file, which will break restore/build until conflict markers are removed and the lockfile is regenerated.

🤖 Fix all issues with AI agents
In `@Flow.Launcher.Avalonia/App.axaml.cs`:
- Around line 91-107: In LoadSettings(), the catch creates a fallback Settings
instance but doesn't attach storage; allocate a new
FlowLauncherJsonStorage<Settings> (same type used in the try) and call
_settings.SetStorage(storage) after constructing the fallback so the _settings
instance has its storage set and subsequent saves/uses won't hit null; reference
LoadSettings, FlowLauncherJsonStorage<Settings>, _settings and SetStorage when
making the change.

In `@Flow.Launcher.Avalonia/AvaloniaPublicAPI.cs`:
- Around line 114-115: The current LoadSettingJsonStorage<T>() and
SaveSettingJsonStorage<T>() stubs return/perform nothing and break plugin
settings persistence; update these methods to delegate to the real JSON storage
implementation in the Infrastructure layer (call the Infrastructure JSON
storage/load and save APIs for type T) instead of returning new T(), and ensure
SaveSettingJsonStorage<T>() forwards the instance to be persisted (or, if no
Infrastructure API exists yet, throw a clear NotImplementedException and
document that Avalonia mode does not support settings persistence); use the
method names LoadSettingJsonStorage<T>() and SaveSettingJsonStorage<T>() as the
integration points to wire to the Infrastructure JSON storage.
- Around line 60-61: ShellRun currently passes the user-supplied cmd directly
into "cmd.exe /c" which allows command injection; update the ShellRun
implementation in AvaloniaPublicAPI.ShellRun to avoid invoking a shell with
unescaped input: either require callers to pass a specific executable and
separate arguments (split/validate the input) and call Process.Start with
UseShellExecute = false and FileName set to the executable and Arguments to a
validated args string, or implement strict validation/whitelisting of allowed
commands when invoking cmd.exe; ensure you do not concatenate raw cmd into "/c
{cmd}" and mirror the safer behavior in the WPF implementation or document the
security tradeoffs.

In `@Flow.Launcher.Avalonia/Helper/GlobalHotkey.cs`:
- Around line 190-271: Concurrent access to _hotkeyCallbacks and _nextHotkeyId
can corrupt state; add a private static readonly object (e.g., _hotkeyLock) and
use lock(_hotkeyLock) to serialize all accesses and mutations: wrap the id
generation and _hotkeyCallbacks assignment in Register, the Remove call and
UnregisterHotKey call in Unregister, the iteration/unregister/clear sequence in
Shutdown (or copy keys to a list inside the lock then operate), and the lookup
in WndProc (and any other reads/writes like ProcessMessages if it touches the
same state) so Dictionary reads/writes and _nextHotkeyId increments are
protected.

In `@Flow.Launcher.Avalonia/Resource/Internationalization.cs`:
- Around line 45-74: Initialize currently continues even if core language
resources are missing, leaving _translations empty and UI showing placeholders;
modify Initialize to stop and return early when AddFlowLauncherLanguageDirectory
indicates the core language directory is missing or when
LoadLanguageFile(DefaultLanguageCode) fails to populate translations: after
calling AddFlowLauncherLanguageDirectory() check its result (or verify the
expected directory/file exists) and if missing Log.Exception/Log.Error with
context and return; likewise, after LoadLanguageFile(DefaultLanguageCode) verify
_translations is non-empty and if empty Log.Error and return before attempting
ChangeCultureInfo or loading a secondary language; use the existing methods
AddFlowLauncherLanguageDirectory, LoadLanguageFile, DefaultLanguageCode,
_translations and ChangeCultureInfo to locate and implement these guards.
- Around line 278-297: ChangeLanguage currently treats the literal "system" as a
language and thus falls back to DefaultLanguageCode; normalize the incoming
languageCode at the start of ChangeLanguage by detecting a case-insensitive
"system" and replacing it with the actual system UI language (e.g.,
CultureInfo.CurrentUICulture.Name or TwoLetterISOLanguageName as appropriate for
your language file keys) before clearing _translations and calling
LoadLanguageFile, ChangeCultureInfo and InjectIntoApplicationResources so the
real system language is loaded and applied instead of the English fallback.

In `@Flow.Launcher.Avalonia/ViewModel/ResultsViewModel.cs`:
- Around line 57-79: Before calling _sourceList.EditDiff in ReplaceResults,
capture the previous selection (e.g., the current SelectedItem or SelectedIndex
and its identifying keys such as Title and SubTitle on ResultViewModel). After
EditDiff completes, try to find the same item in the updated _results using the
same identity used by ResultViewModelComparer (match Title+SubTitle or expose a
method/property used for comparison); if found set SelectedIndex and
SelectedItem to that index/item; if not found fall back to selecting the first
item (index 0) when _results has entries or set SelectedItem = null and
SelectedIndex = -1 when empty. Ensure you still set r.Settings for each incoming
result before EditDiff.
- Around line 143-160: The ResultViewModelComparer currently only compares Title
and SubTitle causing cross-plugin collisions; update ResultViewModelComparer
(Equals and GetHashCode) to include the plugin identity by incorporating
ResultViewModel.PluginResult.PluginID (or prefer PluginResult.RecordKey when
available) into the equality key so distinct plugin results aren’t treated as
equal; modify Equals to compare PluginResult.PluginID or RecordKey along with
Title/SubTitle and update GetHashCode to combine PluginID/RecordKey with Title
and SubTitle accordingly.

In `@Flow.Launcher.Avalonia/ViewModel/ResultViewModel.cs`:
- Around line 22-63: Implement partial change handlers for the observable
properties to invalidate the cached image task and raise property change
notifications: add OnIconPathChanged(string? old, string? new) to set _imageTask
= null and call OnPropertyChanged(nameof(Image)) and
OnPropertyChanged(nameof(ShowIcon)); add OnSubTitleChanged(...) to call
OnPropertyChanged(nameof(ShowSubTitle)); and add OnSettingsChanged(...) to call
OnPropertyChanged(nameof(ShowGlyph)) (or the proper computed property name for
glyph visibility). Reference the members IconPath, SubTitle, Settings,
_imageTask, Image, ShowIcon, ShowSubTitle and ShowGlyph so the UI updates when
those values change.

In
`@Flow.Launcher.Avalonia/ViewModel/SettingPages/PluginStoreSettingsViewModel.cs`:
- Around line 174-194: The callback passed to
PluginInstaller.CheckForPluginUpdatesAsync in CheckPluginUpdatesAsync is empty
so users never see results; update it to surface feedback: when the callback
receives a non-empty plugins list, open the update UI if PluginUpdateWindow is
available (reference PluginUpdateWindow) or else show a simple
message/notification to the user (use the app's existing message/notification
service) indicating which plugins have updates, and when the list is empty show
a "no updates" message; also add a bool property like IsCheckingForPluginUpdates
and set it while awaiting to disable the command during the check so the command
cannot be re-invoked.

In `@Flow.Launcher.Avalonia/Views/Controls/HotkeyControl.axaml.cs`:
- Around line 64-87: OpenHotkeyRecorderDialog temporarily removes the global
hotkey via HotKeyMapper.RemoveToggleHotkey but only re-registers it on the
cancel path; after Save or Delete you must re-register the new state. Update
OpenHotkeyRecorderDialog so that when result ==
HotkeyRecorderDialog.EResultType.Save (after Hotkey = dialog.ResultValue) and
when result == HotkeyRecorderDialog.EResultType.Delete (after Hotkey =
string.Empty) you call HotKeyMapper.SetToggleHotkey(Hotkey) to re-register the
updated hotkey, leaving the existing restore-on-cancel behavior intact.

In `@Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs`:
- Around line 24-27: GetKeyFromVk currently exposes WPF's
System.Windows.Input.Key from the shared Infrastructure, coupling Avalonia UI to
WPF types; refactor by removing GetKeyFromVk from
Flow.Launcher.Infrastructure.Hotkey.GlobalHotkey and either (a) move its
implementation into a WPF-only assembly (e.g. the Flow.Launcher.Avalonia/WPF
project) and update HotkeyRecorderDialog.axaml.cs to call the new WPF-local
converter, or (b) introduce a framework-neutral abstraction in Infrastructure
(e.g. an enum HotkeyKey or an interface IKeyConverter) that GlobalHotkey
returns, and implement platform-specific converters in WPF and Avalonia layers
(use injection or a static platform-specific converter) so
HotkeyRecorderDialog.axaml.cs no longer depends on System.Windows.Input.Key.

In `@Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs`:
- Around line 444-452: The fallback branch in EditQuickAccessLinkAsync and
AddQuickAccessLinkAsync unconditionally sets dialogResult = true; instead,
mirror the fix used in EditActionKeywordAsync (or extract a helper like
ShowDialogWithFallbackAsync(Window dialog)): create a
TaskCompletionSource<bool?>, wire dialog.Closed to
tcs.TrySetResult(dialog.DialogResult), await tcs.Task, and then set dialogResult
= (result == true) so Save() only runs when the dialog.result is true; update
both methods (and the similar block at the other location) to use this pattern
or call the shared helper.
- Around line 319-327: The fallback path for modal dialogs in
EditActionKeywordAsync (and the two other dialog methods) incorrectly forces
dialogResult = true after dialog.Show(); instead capture the actual bool? result
that the dialog returns: add a temporary variable (or a public Result/Selected
property on ActionKeywordSetting) and, in the dialog.Closed handler, read that
value (set by the dialog before it closes) and call tcs.TrySetResult(result)
instead of true; then await tcs.Task and assign dialogResult from that captured
value. Update the three occurrences (the fallback Show()/Closed handlers) to use
this pattern so user cancellations produce false/null instead of always true.

In `@Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/PreviewPanel.axaml.cs`:
- Around line 134-149: The Bitmap(imagePath) call in LoadImageAsync blocks the
UI thread; change LoadImageAsync to create/decode the Bitmap inside Task.Run
(off the UI thread) and then marshal the resulting Bitmap back onto the UI
thread via global::Avalonia.Threading.Dispatcher.UIThread.Post to set
PreviewImage; preserve existing exception handling (log via
Main.Context.API.LogException with ClassName and FilePath) and follow the same
pattern used by GetFolderSize() and ImageLoader.cs to avoid UI freezes when
loading large images.

In `@Plugins/Flow.Launcher.Plugin.Program/ViewModels/ProgramSettingViewModel.cs`:
- Around line 203-209: The Reindex method sets IsIndexing true but never resets
it if Main.IndexProgramsAsync throws; wrap the await Main.IndexProgramsAsync()
call in a try/finally (or try/catch/finally) inside the Reindex method so that
IsIndexing is set to false in the finally block, ensuring IsIndexing is always
reset regardless of exceptions thrown by Main.IndexProgramsAsync.
🟡 Minor comments (25)
Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj-65-67 (1)

65-67: ⚠️ Potential issue | 🟡 Minor

Update Avalonia and FluentAvaloniaUI packages to recent stable versions.

Current versions are outdated:

  • Avalonia 11.2.3 → Latest stable: 11.3.11
  • CommunityToolkit.Mvvm 8.4.0 → Already at latest stable
  • FluentAvaloniaUI 2.2.0 → Latest stable: 2.5.0

Version mismatches between Avalonia (0.1.8 behind) and FluentAvaloniaUI (0.3.0 behind) can cause runtime compatibility issues. Update to 11.3.x and 2.4.0+ respectively.

Plugins/Flow.Launcher.Plugin.Shell/Converters/Avalonia/LeaveShellOpenOrCloseShellAfterPressEnabledConverter.cs-10-19 (1)

10-19: ⚠️ Potential issue | 🟡 Minor

Return AvaloniaProperty.UnsetValue when bindings aren't ready, not false.

In Avalonia converters, returning false on null/invalid inputs can prematurely set the binding target to disabled. Per Avalonia 11 documentation, return AvaloniaProperty.UnsetValue so the binding engine defers evaluation until all required values are present.

Suggested fix
 using System;
 using System.Collections.Generic;
 using System.Globalization;
+using Avalonia;
 using Avalonia.Data.Converters;

 namespace Flow.Launcher.Plugin.Shell.Converters.Avalonia
 {
     public class LeaveShellOpenOrCloseShellAfterPressEnabledConverter : IMultiValueConverter
     {
         public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture)
         {
             if (values == null || values.Count != 2)
-                return false;
+                return AvaloniaProperty.UnsetValue;

             if (values[0] is not bool closeShellAfterPressOrLeaveShellOpen ||
                 values[1] is not Shell shell)
-                return false;
+                return AvaloniaProperty.UnsetValue;

             return (!closeShellAfterPressOrLeaveShellOpen) && shell != Shell.RunCommand;
         }
     }
 }
Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj-62-71 (1)

62-71: ⚠️ Potential issue | 🟡 Minor

Prevent stale artifacts in Avalonia plugin output.

Line 62-71: The copy target only adds/updates files; deleted outputs in $(OutputPath) will remain in $(AvaloniaPluginDir). That can leave stale DLLs/resources and cause confusing runtime behavior. Consider cleaning the destination before copying.

🧹 Suggested cleanup before copy
   <Target Name="CopyToAvaloniaOutput" AfterTargets="Build">
     <PropertyGroup>
       <AvaloniaPluginDir>..\..\Output\$(Configuration)\Avalonia\Plugins\$(AssemblyName)\</AvaloniaPluginDir>
     </PropertyGroup>
     <ItemGroup>
       <PluginFiles Include="$(OutputPath)**\*.*" />
     </ItemGroup>
+    <RemoveDir Directories="$(AvaloniaPluginDir)" Condition="Exists('$(AvaloniaPluginDir)')" />
     <Copy SourceFiles="@(PluginFiles)" DestinationFolder="$(AvaloniaPluginDir)%(RecursiveDir)" SkipUnchangedFiles="true" />
   </Target>
Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs-21-23 (1)

21-23: ⚠️ Potential issue | 🟡 Minor

Snapshot the public delegate before invoke to avoid a race.

Because hookedKeyboardCallback can now be swapped from outside, the current null-check + invoke can race and throw. Capture to a local before invoking.

🔧 Suggested fix
-                    if (hookedKeyboardCallback != null)
-                        continues = hookedKeyboardCallback((KeyEvent)wParam.Value, Marshal.ReadInt32(lParam), CheckModifiers());
+                    var callback = hookedKeyboardCallback;
+                    if (callback != null)
+                        continues = callback((KeyEvent)wParam.Value, Marshal.ReadInt32(lParam), CheckModifiers());

Also applies to: 82-83

Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs-540-570 (1)

540-570: ⚠️ Potential issue | 🟡 Minor

initialDirectory parameter is unused in both picker methods.

The initialDirectory parameter is accepted but never passed to the picker options. Avalonia's FolderPickerOpenOptions and FilePickerOpenOptions support SuggestedStartLocation which should be set from the initial directory.

🔧 Proposed fix to use initialDirectory
 private static async Task<string?> PromptUserSelectFolderAsync(string? initialDirectory = null)
 {
     var mainWindow = AvaloniaApp.Current?.ApplicationLifetime is global::Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktop
         ? desktop.MainWindow
         : null;

     if (mainWindow == null) return null;

+    global::Avalonia.Platform.Storage.IStorageFolder? startLocation = null;
+    if (!string.IsNullOrEmpty(initialDirectory) && Directory.Exists(initialDirectory))
+    {
+        startLocation = await mainWindow.StorageProvider.TryGetFolderFromPathAsync(initialDirectory);
+    }
+
     var folders = await mainWindow.StorageProvider.OpenFolderPickerAsync(new global::Avalonia.Platform.Storage.FolderPickerOpenOptions
     {
-        AllowMultiple = false
+        AllowMultiple = false,
+        SuggestedStartLocation = startLocation
     });

     return folders.Count > 0 ? folders[0].Path.LocalPath : null;
 }

Apply similar changes to PromptUserSelectFileAsync.

Plugins/Flow.Launcher.Plugin.WebSearch/Views/Avalonia/SettingsControl.axaml.cs-27-71 (1)

27-71: ⚠️ Potential issue | 🟡 Minor

Localize dialog strings instead of hard‑coded English.

The Add/Edit/Delete dialogs use hard‑coded strings, which bypasses i18n and results in English‑only UI. Please route these through the translation system (e.g., API.GetTranslation or the Avalonia i18n helpers) and add the corresponding keys.

Flow.Launcher.Avalonia/Helper/FontLoader.cs-155-188 (1)

155-188: ⚠️ Potential issue | 🟡 Minor

Parse file URIs with Uri to handle UNC paths and URL‑encoded characters.

The manual file:// substring logic fails to:

  • Convert UNC paths (file://server/share/path) to proper Windows UNC format (\\server\share\path), causing File.Exists() to fail
  • Decode URL-encoded characters (%20, %21, etc.), preventing file discovery when paths contain encoded spaces or special characters

Use Uri.TryCreate() with Uri.LocalPath and Uri.Fragment to normalize and decode file paths correctly. The approach safely falls back to manual parsing for raw local paths.

🔧 Suggested fix
 private static FontFamily? LoadFontFromFile(string fontFamilyPath)
 {
     try
     {
-        var filePath = fontFamilyPath;
-        if (filePath.StartsWith("file:///", StringComparison.OrdinalIgnoreCase))
-            filePath = filePath.Substring(8);
-        else if (filePath.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
-            filePath = filePath.Substring(7);
-        
-        var hashIndex = filePath.IndexOf('#');
-        string fontFilePath;
-        string? fontName;
+        string filePath = fontFamilyPath;
+        string? fontName = null;
+
+        if (Uri.TryCreate(fontFamilyPath, UriKind.Absolute, out var uri) && uri.IsFile)
+        {
+            filePath = uri.LocalPath;
+            if (!string.IsNullOrEmpty(uri.Fragment))
+                fontName = uri.Fragment[1..];
+        }
+
+        var hashIndex = filePath.IndexOf('#');
+        string fontFilePath;
         
         if (hashIndex >= 0)
         {
             fontFilePath = filePath.Substring(0, hashIndex);
-            fontName = filePath.Substring(hashIndex + 1);
+            fontName ??= filePath.Substring(hashIndex + 1);
         }
         else
         {
             fontFilePath = filePath;
-            fontName = null;
         }
Flow.Launcher.Avalonia/Converters/CommonConverters.cs-68-84 (1)

68-84: ⚠️ Potential issue | 🟡 Minor

Converter always returns empty, so suggestions never show.
If this binding is used in the UI, the suggestion text will stay blank. Either implement the logic or remove/gate the binding to avoid a dead feature surface.

Flow.Launcher.Avalonia/ViewModel/SettingPages/GeneralSettingsViewModel.cs-236-244 (1)

236-244: ⚠️ Potential issue | 🟡 Minor

Clamp negative search delay values.
A negative delay can cause downstream timing issues; clamp to >= 0.

Proposed fix
     public int SearchDelayTime
     {
         get => _settings.SearchDelayTime;
         set
         {
-            _settings.SearchDelayTime = value;
+            var clamped = Math.Max(0, value);
+            _settings.SearchDelayTime = clamped;
             OnPropertyChanged();
         }
     }
Flow.Launcher.Avalonia/Converters/CommonConverters.cs-90-101 (1)

90-101: ⚠️ Potential issue | 🟡 Minor

Modulo repeats hotkeys beyond 10.
For indices ≥10, (index + 1) % 10 repeats 1–0, which can mislead users if more than 10 results render. Consider clamping after 10 or returning empty.

Proposed fix (clamp after 10)
     public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
     {
         if (value is int index)
         {
-            // Convert 0-based index to 1-based display, wrapping 9 to 0
-            var displayNumber = (index + 1) % 10;
-            return displayNumber.ToString();
+            // 0-8 -> 1-9, 9 -> 0, >=10 -> no hotkey
+            if (index < 9) return (index + 1).ToString();
+            if (index == 9) return "0";
+            return string.Empty;
         }
         return "0";
     }
Flow.Launcher.Avalonia/Converters/CommonConverters.cs-112-121 (1)

112-121: ⚠️ Potential issue | 🟡 Minor

Parse ratio using invariant culture.
XAML parameters use .; double.TryParse with current culture can fail on locales using ,.

Proposed fix (InvariantCulture parsing)
-        if (value is double size && parameter is string ratioStr && double.TryParse(ratioStr, out var ratio))
+        if (value is double size &&
+            parameter is string ratioStr &&
+            double.TryParse(ratioStr, NumberStyles.Float, CultureInfo.InvariantCulture, out var ratio))
         {
             return size * ratio;
         }
Plugins/Flow.Launcher.Plugin.Program/ViewModels/ProgramSettingViewModel.cs-91-101 (1)

91-101: ⚠️ Potential issue | 🟡 Minor

Guard against null/whitespace in custom suffix/protocol inputs.
If the binding passes null, Split will throw. Also trimming entries avoids accidental spaces.

Proposed fix
     partial void OnCustomSuffixesChanged(string value)
     {
-        _settings.CustomSuffixes = value.Split(Settings.SuffixSeparator, StringSplitOptions.RemoveEmptyEntries);
+        _settings.CustomSuffixes = (value ?? string.Empty)
+            .Split(Settings.SuffixSeparator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
         Main.ResetCache();
     }

     partial void OnCustomProtocolsChanged(string value)
     {
-        _settings.CustomProtocols = value.Split(Settings.SuffixSeparator, StringSplitOptions.RemoveEmptyEntries);
+        _settings.CustomProtocols = (value ?? string.Empty)
+            .Split(Settings.SuffixSeparator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
         Main.ResetCache();
     }
Flow.Launcher.Avalonia/Views/Controls/ExCard.axaml-17-29 (1)

17-29: ⚠️ Potential issue | 🟡 Minor

Remove the #ExpanderHeader identifier from the ToggleButton selector.

Avalonia 11.2.3's Expander does not define template parts, so ToggleButton#ExpanderHeader won't match anything in the default template. Use Expander /template/ ToggleButton instead to target the header button and apply the sizing/padding.

Corrected selector
<Style Selector="Expander /template/ ToggleButton">
    <Setter Property="MinHeight" Value="68" />
    <Setter Property="Padding" Value="0" />
</Style>
Plugins/Flow.Launcher.Plugin.Program/Main.cs-422-428 (1)

422-428: ⚠️ Potential issue | 🟡 Minor

Remove debug Console.WriteLine statement.

Line 424 contains a Console.WriteLine debug statement that should be removed before merging. The LogInfo call on line 425 already provides sufficient logging.

Proposed fix
 public AvaloniaControl CreateSettingPanelAvalonia()
 {
-    System.Console.WriteLine("Program plugin: CreateSettingPanelAvalonia called!");
     Context.API.LogInfo(ClassName, "Creating Avalonia setting panel");
     return new Views.Avalonia.ProgramSetting(Context, _settings);
 }
-
Flow.Launcher.Avalonia/AvaloniaPublicAPI.cs-69-70 (1)

69-70: ⚠️ Potential issue | 🟡 Minor

OpenDirectory ignores the FileNameOrFilePath parameter.

The FileNameOrFilePath parameter is ignored, which means callers expecting to select a specific file within the directory won't get the expected behavior.

Proposed fix to select file in explorer
-public void OpenDirectory(string DirectoryPath, string? FileNameOrFilePath = null) => 
-    System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = DirectoryPath, UseShellExecute = true });
+public void OpenDirectory(string DirectoryPath, string? FileNameOrFilePath = null)
+{
+    if (!string.IsNullOrEmpty(FileNameOrFilePath))
+    {
+        var fullPath = Path.Combine(DirectoryPath, FileNameOrFilePath);
+        if (File.Exists(fullPath))
+        {
+            System.Diagnostics.Process.Start("explorer.exe", $"/select,\"{fullPath}\"");
+            return;
+        }
+    }
+    System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = DirectoryPath, UseShellExecute = true });
+}
Flow.Launcher.Avalonia/AvaloniaPublicAPI.cs-131-131 (1)

131-131: ⚠️ Potential issue | 🟡 Minor

LoadImageAsync returns null which could cause NullReferenceException.

Returning (ImageSource)null! suppresses the null warning but plugins calling this method may not expect null and could throw NRE.

Proposed fix

Consider returning a default/placeholder image or implementing the actual image loading:

-public ValueTask<ImageSource> LoadImageAsync(string path, bool loadFullImage = false, bool cacheImage = true) => new((ImageSource)null!);
+public ValueTask<ImageSource> LoadImageAsync(string path, bool loadFullImage = false, bool cacheImage = true)
+{
+    // TODO: Implement actual image loading or return a placeholder
+    return new ValueTask<ImageSource>(Task.FromResult<ImageSource>(null!));
+}
Flow.Launcher.Avalonia/Views/SettingPages/AboutSettingsPage.axaml-23-23 (1)

23-23: ⚠️ Potential issue | 🟡 Minor

Outdated copyright year.

The copyright text shows "2024" but the current year is 2026. Consider using a dynamic approach or updating to reflect the current year.

Proposed fix
-        <TextBlock Text="Copyright © 2024 Flow Launcher" Foreground="Gray" FontSize="12" HorizontalAlignment="Center" Margin="0,50,0,0" />
+        <TextBlock Text="Copyright © 2025 Flow Launcher" Foreground="Gray" FontSize="12" HorizontalAlignment="Center" Margin="0,50,0,0" />

Alternatively, bind to a dynamic year property from the ViewModel.

Flow.Launcher.Avalonia/Converters/TranslationConverter.cs-16-21 (1)

16-21: ⚠️ Potential issue | 🟡 Minor

Align TranslationConverter fallback behavior with Translator.GetString.

The code correctly uses Translator.GetString(key), but the fallback on line 21 returns "[No Translation]" while Translator.GetString returns "[{key}]" on lookup failure. This inconsistency could result in confusing UX—sometimes the missing key is shown (aiding debugging), sometimes a generic message appears. Consider using $"[{key}]" for consistency with Translator.GetString and LocalizeExtension.

Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/Avalonia/SettingsControl.axaml-13-78 (1)

13-78: ⚠️ Potential issue | 🟡 Minor

Localize the remaining hard‑coded strings.

The two Description values and the "Options" header are hard-coded English while the rest of the UI uses localized resources. Please move these strings to the localization resources (or reuse an existing key).

🌐 Suggested change
-        <ui:SettingsExpander Header="{DynamicResource flowlauncher_plugin_browserbookmark_loadBrowserFrom}"
-                             IconSource="Globe"
-                             Description="Select which browsers to load bookmarks from"
+        <ui:SettingsExpander Header="{DynamicResource flowlauncher_plugin_browserbookmark_loadBrowserFrom}"
+                             IconSource="Globe"
+                             Description="{DynamicResource flowlauncher_plugin_browserbookmark_loadBrowserFromDesc}"
                              IsExpanded="True">
...
-        <ui:SettingsExpander Header="{DynamicResource flowlauncher_plugin_browserbookmark_others}"
-                             IconSource="Library"
-                             Description="Manage custom Chromium-based browsers">
+        <ui:SettingsExpander Header="{DynamicResource flowlauncher_plugin_browserbookmark_others}"
+                             IconSource="Library"
+                             Description="{DynamicResource flowlauncher_plugin_browserbookmark_manageCustomChromiumDesc}">
...
-        <ui:SettingsExpander Header="Options" IconSource="Settings">
+        <ui:SettingsExpander Header="{DynamicResource flowlauncher_plugin_browserbookmark_options}" IconSource="Settings">
Flow.Launcher.Avalonia/Views/SettingPages/PluginStoreSettingsPage.axaml-35-39 (1)

35-39: ⚠️ Potential issue | 🟡 Minor

Localize the filter labels.

Lines 35–39 use hard-coded strings (“C# / .Net”, “Python”, etc.). Please route these through i18n:Localize to keep translation coverage consistent.

Flow.Launcher.Avalonia/ViewModel/ResultViewModel.cs-83-85 (1)

83-85: ⚠️ Potential issue | 🟡 Minor

Glyph visibility won't update when UseGlyphIcons changes.

ShowGlyph depends on Settings?.UseGlyphIcons, but UseGlyphIcons is a plain auto-property that doesn't raise PropertyChanged notifications when toggled. The ShowGlyph computed property won't update in the UI. Either make UseGlyphIcons notify on changes using a backing field (like other settings do), or subscribe to Settings changes in ResultViewModel and propagate OnPropertyChanged(nameof(ShowGlyph)).

Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/Avalonia/SettingsControl.axaml.cs-19-22 (1)

19-22: ⚠️ Potential issue | 🟡 Minor

Parameterless constructor is redundant for current runtime usage.

The runtime instantiation in Main.cs:226 always calls new SettingsControl(_settings), which uses the parameterized constructor that correctly sets DataContext to a SettingsViewModel instance. The parameterless constructor (lines 19-22) is never invoked at runtime and doesn't set DataContext, creating dead code and potential confusion.

Either document the parameterless constructor's purpose as design-time only via a comment, or remove it if it serves no function. If needed for XAML editor support, add an explanatory comment above it.

Flow.Launcher.Avalonia/ViewModel/SettingPages/PluginStoreSettingsViewModel.cs-61-125 (1)

61-125: ⚠️ Potential issue | 🟡 Minor

Guard against null descriptions during filtering.
If a plugin manifest contains a null description, FuzzySearch may throw. Safest fix is to coalesce to an empty string.

🔧 Suggested fix
-            var descMatch = App.API.FuzzySearch(FilterText, plugin.Description);
+            var descMatch = App.API.FuzzySearch(FilterText, plugin.Description ?? string.Empty);
Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/QuickAccessLinkSettings.axaml.cs-23-40 (1)

23-40: ⚠️ Potential issue | 🟡 Minor

Keep file/folder toggles in sync with the inferred path type.
When the user types a path manually, _accessLinkType updates but the toggle flags can stay stale, so the UI can show Folder while saving as File. Update the toggles when the path changes.

🔧 Suggested fix
             if (!string.IsNullOrEmpty(_selectedPath))
             {
                 _accessLinkType = GetResultType(_selectedPath);
+                var isFile = _accessLinkType == ResultType.File;
+                IsFileSelected = isFile;
+                IsFolderSelected = !isFile;
             }
Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/QuickAccessLinkSettings.axaml.cs-131-179 (1)

131-179: ⚠️ Potential issue | 🟡 Minor

Validate path existence before saving a link.
Empty checks pass, but a non-existent path can still be saved, creating a broken quick-access entry. Add an existence guard before dedupe/update (and add a localized message for invalid paths).

🔧 Suggested fix
         if (string.IsNullOrEmpty(SelectedName) || string.IsNullOrEmpty(SelectedPath))
         {
             var warning = Localize.plugin_explorer_quick_access_link_no_folder_selected();
             Main.Context.API.ShowMsgBox(warning);
             return;
         }
+
+        if (!File.Exists(SelectedPath) && !Directory.Exists(SelectedPath))
+        {
+            var warning = Localize.plugin_explorer_quick_access_link_invalid_path();
+            Main.Context.API.ShowMsgBox(warning);
+            return;
+        }
🧹 Nitpick comments (36)
Flow.Launcher.Infrastructure/Logger/Log.cs (1)

152-154: Consider wrapping console output in #if DEBUG preprocessor directive.

This unconditional Console.WriteLine call will execute in both Debug and Release builds. Given the comment indicates this is "for easy debugging," it should likely be conditional. This file already uses #if DEBUG for similar debug-only behavior (e.g., the debug output target at lines 55-61 and exception re-throwing at lines 100-106).

♻️ Suggested fix
             logger.Log(level, message);

+#if DEBUG
             // Also output to console for easy debugging
             System.Console.WriteLine($"[{level}] {classNameWithMethod}: {message}");
+#endif
         }
AVALONIA_MIGRATION_CHECKLIST.md (1)

413-433: Add language specifiers to fenced code blocks.

The code blocks showing directory structures should specify a language (e.g., text or plaintext) to satisfy markdown linting rules and improve rendering consistency.

📝 Suggested fix
-```
+```text
 Flow.Launcher/
 ├── MainWindow.xaml (+ .cs)

Apply the same fix to the code block starting at line 436.

Plugins/Flow.Launcher.Plugin.PluginsManager/Views/Avalonia/PluginsManagerSettings.axaml.cs (1)

14-18: Consider consistent initialization order across Avalonia views.

Here DataContext is set before InitializeComponent(), but in ExplorerSettings.axaml.cs (lines 20-21) the order is reversed (InitializeComponent() then DataContext). While both work in Avalonia, a consistent pattern would improve maintainability.

Flow.Launcher.Avalonia/Views/ResultListBox.axaml (1)

115-126: Replace hardcoded colors with theme resources for consistency.

The selection and hover state colors are hardcoded (#30FFFFFF, #20FFFFFF), which breaks theme consistency. Other parts of this file correctly use DynamicResource for colors.

♻️ Suggested refactor using theme resources
                 <Style Selector="^:selected">
-                    <Setter Property="Background" Value="#30FFFFFF" />
+                    <Setter Property="Background" Value="{DynamicResource ListViewItemBackgroundSelected}" />
                 </Style>
                 
                 <Style Selector="^:selected /template/ ContentPresenter#PART_ContentPresenter">
-                    <Setter Property="Background" Value="#30FFFFFF" />
+                    <Setter Property="Background" Value="{DynamicResource ListViewItemBackgroundSelected}" />
                 </Style>
                 
                 <Style Selector="^:pointerover">
-                    <Setter Property="Background" Value="#20FFFFFF" />
+                    <Setter Property="Background" Value="{DynamicResource ListViewItemBackgroundPointerOver}" />
                 </Style>

If these specific resources don't exist, consider defining them in your theme resources file to maintain theming flexibility.

Flow.Launcher.Avalonia/Helper/FontLoader.cs (1)

50-73: Support generic avares:// embedded font URIs for extensibility.

TryGetEmbeddedFont only handles Segoe Fluent Icons; any other embedded font URI (like avares://Flow.Launcher.Avalonia/Resources#CustomFont) would fail to resolve. While currently unused, supporting generic embedded fonts would improve extensibility for plugins or future UI enhancements.

🔧 Suggested fix
 private static FontFamily? TryGetEmbeddedFont(string fontFamilyPath)
 {
     try
     {
         var fontName = ExtractFontName(fontFamilyPath);
         if (string.IsNullOrEmpty(fontName))
             return null;
 
         // Check for Segoe Fluent Icons specifically (common for Flow Launcher plugins)
         if (fontName.Contains("Segoe Fluent Icons", StringComparison.OrdinalIgnoreCase))
         {
             // Try to get from Application Resources first
             if (Application.Current != null && Application.Current.TryGetResource("SegoeFluentIcons", null, out var resource))
             {
                 if (resource is FontFamily family)
                     return family;
             }
             
             // Fallback to direct URI - Folder based is usually better in Avalonia 11
             return SafeCreateFontFamily("avares://Flow.Launcher.Avalonia/Resources#Segoe Fluent Icons");
         }
+
+        if (fontFamilyPath.StartsWith("avares://", StringComparison.OrdinalIgnoreCase))
+            return SafeCreateFontFamily(fontFamilyPath);
 
         return null;
     }
     catch
     {
         return null;
     }
 }
Plugins/Flow.Launcher.Plugin.Shell/Views/Avalonia/ShellSetting.axaml.cs (1)

9-18: Parameterless constructor leaves DataContext unset.

The parameterless constructor (likely for designer support) doesn't initialize DataContext, which could cause binding errors in the XAML designer. Consider providing a design-time DataContext or documenting that only the Settings constructor should be used at runtime.

💡 Optional: Add design-time DataContext
 public ShellSetting()
 {
     InitializeComponent();
+    // Design-time DataContext for XAML previewer
+    if (Design.IsDesignMode)
+        DataContext = new ShellSettingViewModel(new Settings());
 }

This requires adding using Avalonia at the top for Design.IsDesignMode.

Flow.Launcher.Avalonia/Helper/ImageLoader.cs (1)

24-27: Add a timeout to HttpClient to avoid hanging icon loads.

A reasonable timeout prevents stalled network requests from tying up threads indefinitely.

♻️ Proposed change
-    private static readonly HttpClient _httpClient = new();
+    private static readonly HttpClient _httpClient = new()
+    {
+        Timeout = TimeSpan.FromSeconds(10)
+    };
Flow.Launcher.Avalonia/Helper/TextBlockHelper.cs (1)

32-55: Consider cloning non-Run inlines to avoid parent-ownership issues.

Non-Run inline instances added directly may already belong to another TextBlock; cloning or documenting a “unique instance” requirement avoids runtime exceptions.

Flow.Launcher.Avalonia/ViewModel/SettingPages/PluginStoreItemViewModel.cs (1)

36-37: Consider defensive version parsing for plugin store data.

The new Version() constructor throws on invalid strings. While PluginManager validates metadata from disk using Version.TryParse(), UserPlugin data from the store may not have the same guarantees. A try-catch fallback is optional but would harden against malformed store data:

♻️ Optional defensive guard
-        public bool LabelUpdate => LabelInstalled && new Version(_newPlugin.Version) > new Version(_oldPluginPair.Metadata.Version);
+        public bool LabelUpdate
+        {
+            get
+            {
+                if (!LabelInstalled) return false;
+                try
+                {
+                    return new Version(_newPlugin.Version) > new Version(_oldPluginPair.Metadata.Version);
+                }
+                catch
+                {
+                    return false;
+                }
+            }
+        }
Flow.Launcher.Avalonia/Views/Controls/Card.axaml (1)

65-232: Consider extracting a shared template to reduce duplication.
The inside/insidefit/first/middle/last variants repeat the same Grid/Content structure; a base style/theme with overridable padding/border properties would reduce drift risk.

Flow.Launcher.Avalonia/ViewModel/SettingPages/GeneralSettingsViewModel.cs (1)

331-343: SelectPython/SelectNode are no-ops.
If these commands are exposed in the UI, users will click them with no effect. Consider disabling the UI until implemented, or add the picker logic.

Plugins/Flow.Launcher.Plugin.Program/Views/Avalonia/ProgramSetting.axaml (2)

12-148: Hardcoded English strings should use localization.

This view uses hardcoded English strings (e.g., "Index Sources", "Start Menu", "Registry", "Program Options", etc.) while other Avalonia views in this PR (like PluginsSettingsPage.axaml) use {i18n:Localize} for internationalization. For consistency and to support non-English users, these strings should be localized.

Example refactor for one section
-<ui:SettingsExpander Header="Index Sources"
+<ui:SettingsExpander Header="{i18n:Localize plugin_program_index_sources}"
                      IconSource="Library"
-                     Description="Configure which sources to scan for programs">
+                     Description="{i18n:Localize plugin_program_index_sources_description}">

128-128: Use a dynamic resource instead of hardcoded "Gray" foreground.

Line 128 uses a hardcoded Foreground="Gray" which won't adapt to theme changes. Use a dynamic resource for consistency with the rest of the UI.

Proposed fix
-<TextBlock Text="{Binding Location}" FontSize="12" Foreground="Gray" TextTrimming="CharacterEllipsis" />
+<TextBlock Text="{Binding Location}" FontSize="12" Foreground="{DynamicResource TextFillColorSecondaryBrush}" TextTrimming="CharacterEllipsis" />
Flow.Launcher.Avalonia/Views/SettingPages/PluginsSettingsPage.axaml (1)

88-88: Consider adding Minimum constraint to Priority NumberBox.

The Priority NumberBox lacks a Minimum constraint. If negative priorities aren't valid, add Minimum="0" to prevent invalid input.

Proposed fix
-<ui:NumberBox Value="{Binding Priority}" SpinButtonPlacementMode="Inline" Width="100" SmallChange="1" LargeChange="10"/>
+<ui:NumberBox Value="{Binding Priority}" SpinButtonPlacementMode="Inline" Width="100" Minimum="0" SmallChange="1" LargeChange="10"/>
Flow.Launcher.Avalonia/ViewModel/SettingPages/PluginsSettingsViewModel.cs (2)

238-245: Event subscription without unsubscription may cause memory leak.

The PropertyChanged event handler subscribed at line 239 is never unsubscribed. Since PluginItemViewModel doesn't implement IDisposable, this could prevent garbage collection of PluginItemViewModel instances if PluginMetadata outlives them.

Consider implementing IDisposable to unsubscribe from the event, or use weak event patterns.


456-464: Action keywords updated without conflict validation.

The new keywords are saved without checking if they conflict with keywords already assigned to other plugins. This could lead to runtime issues or user confusion.

Proposed validation
 if (newKeywords != null)
 {
-    // Validate?
-    // For now just update
+    // Validate keywords don't conflict with other plugins
+    foreach (var keyword in newKeywords)
+    {
+        if (!string.IsNullOrWhiteSpace(keyword) && 
+            App.API.ActionKeywordAssigned(keyword) && 
+            !_plugin.Metadata.ActionKeywords.Contains(keyword))
+        {
+            // Show error or skip conflicting keyword
+            continue;
+        }
+    }
     _plugin.Metadata.ActionKeywords = newKeywords;
     PluginSettingsObject.ActionKeywords = newKeywords;
     OnPropertyChanged(nameof(ActionKeywordsText));
 }
Flow.Launcher.Avalonia/AvaloniaPublicAPI.cs (1)

96-100: Critical settings operations are no-ops.

RestartApp(), SaveAppAllSettings(), SavePluginSettings(), and ReloadAllPluginData() are stubs. While these may not be needed for basic queries, plugins that modify settings and expect persistence will silently fail.

Consider adding TODO comments or logging warnings when these methods are called so developers know settings aren't being persisted:

-public void SaveAppAllSettings() { }
+public void SaveAppAllSettings() 
+{ 
+    Log.Warn(nameof(AvaloniaPublicAPI), "SaveAppAllSettings not yet implemented in Avalonia mode");
+}
.github/workflows/dotnet.yml (1)

93-99: Consider adding a condition to avoid failure if the Avalonia output is not present.

If the Avalonia build is not included in every build configuration or if it fails, this step could cause the workflow to fail. Consider adding an if condition to check for the directory's existence.

💡 Proposed conditional check
       - name: Upload Avalonia Build
         uses: actions/upload-artifact@v5
+        if: always() && hashFiles('Output/Release/Avalonia/**') != ''
         with:
           name: Flow Launcher Avalonia
           path: |
             Output\Release\Avalonia\
           compression-level: 6
Flow.Launcher.Avalonia/Views/SettingPages/SettingsWindow.axaml (1)

9-9: Consider localizing the window title.

The Title is hardcoded as "Flow Launcher Settings" while all menu item labels use the i18n:Localize markup extension. For consistency with the internationalization approach used throughout this PR, consider localizing the title as well.

💡 Proposed localization
-        Title="Flow Launcher Settings"
+        Title="{i18n:Localize flowlaunchersettings}"

This requires adding the corresponding translation key to your language resources.

Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ActionKeywordSetting.axaml (1)

46-56: Add default/cancel button semantics for keyboard UX.

Consider setting Done as default and Cancel as cancel so Enter/Esc behaves predictably in this dialog.

Suggested tweak
-                <Button x:Name="btnCancel"
+                <Button x:Name="btnCancel"
+                        IsCancel="True"
                         Width="145"
                         Height="30"
                         Content="{DynamicResource cancel}"
                         Click="BtnCancel_OnClick" />
-                <Button x:Name="btnDone"
+                <Button x:Name="btnDone"
+                        IsDefault="True"
                         Width="145"
                         Height="30"
                         Classes="accent"
                         Content="{DynamicResource plugin_explorer_actionkeyword_done}"
                         Click="OnDoneButtonClick" />
Flow.Launcher.Avalonia/Views/SettingPages/AboutSettingsPage.axaml (1)

19-20: Inconsistent localization: "GitHub" button text is hardcoded.

The "website" button uses {i18n:Localize website} for localization, but the "GitHub" button has hardcoded English text. For consistency, consider either localizing "GitHub" or documenting that brand names are intentionally not localized.

Flow.Launcher.Avalonia/ViewModel/SettingPages/HotkeySettingsViewModel.cs (1)

5-5: Unused using System; directive.

The System namespace import appears unused in this file.

Proposed fix
 using CommunityToolkit.Mvvm.ComponentModel;
 using CommunityToolkit.Mvvm.DependencyInjection;
 using Flow.Launcher.Infrastructure.UserSettings;
 using Flow.Launcher.Avalonia.Helper;
-using System;
 
 namespace Flow.Launcher.Avalonia.ViewModel.SettingPages;
Flow.Launcher.Avalonia/Views/SettingPages/ProxySettingsPage.axaml (1)

40-44: Enhance password field security with TextInputOptions attributes.

While TextBox with PasswordChar is Avalonia's standard approach for password masking, consider adding TextInputOptions attributes to improve security hints and disable keyboard suggestions:

<TextBox Text="{Binding ProxyPassword}"
         PasswordChar="*"
         MinWidth="200"
         IsEnabled="{Binding ProxyEnabled}"
         TextInputOptions.ContentType="Password"
         TextInputOptions.IsSensitive="True"
         TextInputOptions.ShowSuggestions="False" />

This prevents autocomplete/suggestions, hints the platform this is sensitive data, and aligns with Avalonia password field best practices.

Flow.Launcher.sln (1)

75-75: Align the Flow.Launcher.Avalonia project type GUID with other SDK-style projects.

Flow.Launcher.Avalonia is SDK-style (uses <Project Sdk="Microsoft.NET.Sdk">), but its project type GUID differs from all other SDK-style projects in the solution. Using the same GUID keeps Visual Studio tooling consistent.

♻️ Suggested change
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flow.Launcher.Avalonia", "Flow.Launcher.Avalonia\Flow.Launcher.Avalonia.csproj", "{6B30B56B-7CEA-4868-828D-1460A57ACF47}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Avalonia", "Flow.Launcher.Avalonia\Flow.Launcher.Avalonia.csproj", "{6B30B56B-7CEA-4868-828D-1460A57ACF47}"
Flow.Launcher.Avalonia/ViewModel/SettingPages/AboutSettingsViewModel.cs (1)

15-27: Consider removing unnecessary async/await pattern.

Both OpenWebsite and OpenGitHub methods use await Task.CompletedTask which serves no purpose. Since Process.Start is synchronous, these methods don't need to be async.

♻️ Suggested simplification
 [RelayCommand]
-private async Task OpenWebsite()
+private void OpenWebsite()
 {
     Process.Start(new ProcessStartInfo(Website) { UseShellExecute = true });
-    await Task.CompletedTask;
 }

 [RelayCommand]
-private async Task OpenGitHub()
+private void OpenGitHub()
 {
     Process.Start(new ProcessStartInfo(GitHub) { UseShellExecute = true });
-    await Task.CompletedTask;
 }
Flow.Launcher.Avalonia/Themes/Base.axaml (2)

12-19: Consider using DynamicResource for theme-aware colors.

Multiple styles use hardcoded color values (e.g., #E6202020, #E3E0E3, #505050) instead of DynamicResource references. While this works for a base dark theme, it limits theming flexibility. Some styles like pendingLine and resultBullet correctly use {DynamicResource SystemAccentColor}.

If theme customization is planned, consider defining these colors in Resources.axaml and referencing them via DynamicResource for consistency.

Also applies to: 28-41


187-200: Global ScrollBar style may affect all scrollbars.

The ScrollBar styles (lines 187-200) apply globally without a class selector, affecting all scrollbars in the application. If different scrollbar appearances are needed in settings pages or dialogs, consider adding a class selector like ScrollBar.resultScrollBar to scope these styles.

Flow.Launcher.Avalonia/MainWindow.axaml.cs (2)

31-33: Consider removing unused event subscription.

OnWindowDeactivated is subscribed to but the handler is intentionally empty (Hide() is commented out). If auto-hide behavior isn't planned, consider removing the subscription to avoid the minor overhead.

♻️ Remove unused subscription
-        // Subscribe to window events
-        this.Deactivated += OnWindowDeactivated;

And remove the OnWindowDeactivated method (lines 129-134) if not needed.


44-65: Minor: Duplicate focus logic in OnLoaded and OnOpened.

Both OnLoaded (line 49) and OnOpened (lines 62-63) focus the query text box. Since OnOpened is called after OnLoaded, the focus in OnLoaded is redundant. Consider consolidating the focus logic in OnOpened only.

Flow.Launcher.Avalonia/ViewModel/SettingPages/ProxySettingsViewModel.cs (1)

1-4: Unused import.

The using System; directive on line 4 appears to be unused in this file.

🧹 Proposed fix
 using CommunityToolkit.Mvvm.ComponentModel;
 using CommunityToolkit.Mvvm.DependencyInjection;
 using Flow.Launcher.Infrastructure.UserSettings;
-using System;
Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/Avalonia/CustomBrowserSettingWindow.axaml.cs (2)

6-7: Unused imports.

System.Collections.Generic and System.Linq are not used in this file.

🧹 Proposed fix
 using Avalonia.Controls;
 using Avalonia.Interactivity;
 using Avalonia.Markup.Xaml;
 using Avalonia.Platform.Storage;
 using Flow.Launcher.Plugin.BrowserBookmark.Models;
-using System.Collections.Generic;
-using System.Linq;

44-48: Hardcoded string should use localization.

The folder picker title "Select Bookmark Data Directory" is hardcoded. Consider using localization for consistency with the rest of the application.

Flow.Launcher.Avalonia/Views/ResultListBox.axaml.cs (2)

9-9: Unused field _listBox.

The _listBox field is assigned in the constructor but never used elsewhere in this file. Consider removing it if it's not needed, or document its intended purpose if it's scaffolding for future functionality.


22-33: Empty OnPointerPressed override is placeholder code.

The OnPointerPressed method checks for left button press but contains no actual logic—just comments stating "additional click handling can be added here." If this functionality isn't needed now, consider removing the override to reduce code noise. If it's planned for future work, consider adding a TODO comment with the specific intent.

🧹 Option 1: Remove if not needed
-    protected override void OnPointerPressed(PointerPressedEventArgs e)
-    {
-        base.OnPointerPressed(e);
-        
-        // Handle left click on result item
-        var point = e.GetCurrentPoint(this);
-        if (point.Properties.IsLeftButtonPressed)
-        {
-            // The ListBox handles selection automatically
-            // Additional click handling can be added here
-        }
-    }
🧹 Option 2: Add TODO if planned
     protected override void OnPointerPressed(PointerPressedEventArgs e)
     {
         base.OnPointerPressed(e);
         
-        // Handle left click on result item
+        // TODO: Implement custom click handling for result execution (e.g., double-click to open)
         var point = e.GetCurrentPoint(this);
         if (point.Properties.IsLeftButtonPressed)
         {
             // The ListBox handles selection automatically
-            // Additional click handling can be added here
         }
     }
Flow.Launcher.Avalonia/Helper/HotKeyMapper.cs (1)

26-33: Inconsistent DI service retrieval patterns.

MainViewModel is retrieved with GetRequiredService (throws if not registered), while Settings uses GetService (returns null if not registered). If Settings is truly optional with a fallback (early return on line 32), this is fine. However, if Settings being null is an unexpected/error state, consider using GetRequiredService for consistency.

Flow.Launcher.Avalonia/Views/Controls/HotkeyRecorderDialog.axaml.cs (1)

149-163: Localize the “hotkey already in use” message.
There’s a TODO here; consider wiring this string to the i18n/translation resources.

If you want, I can draft a localized binding or open a follow-up issue.

Comment on lines +91 to +107
private void LoadSettings()
{
try
{
var storage = new FlowLauncherJsonStorage<Settings>();
_settings = storage.Load();
_settings.SetStorage(storage);
}
catch (Exception e)
{
Log.Exception(ClassName, "Settings load failed", e);
_settings = new Settings
{
WindowSize = 580, WindowHeightSize = 42, QueryBoxFontSize = 24,
ItemHeightSize = 50, ResultItemFontSize = 14, ResultSubItemFontSize = 12, MaxResultsToShow = 6
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Ensure fallback settings still have storage attached.

Lines 95–106 create a new Settings instance on load failure but never call SetStorage, which can prevent saves or trigger null usage later. Consider attaching a fresh storage in the catch block.

🛠️ Suggested fix
        catch (Exception e)
        {
            Log.Exception(ClassName, "Settings load failed", e);
-           _settings = new Settings
+           var storage = new FlowLauncherJsonStorage<Settings>();
+           _settings = new Settings
            {
                WindowSize = 580, WindowHeightSize = 42, QueryBoxFontSize = 24,
                ItemHeightSize = 50, ResultItemFontSize = 14, ResultSubItemFontSize = 12, MaxResultsToShow = 6
            };
+           _settings.SetStorage(storage);
        }
🤖 Prompt for AI Agents
In `@Flow.Launcher.Avalonia/App.axaml.cs` around lines 91 - 107, In
LoadSettings(), the catch creates a fallback Settings instance but doesn't
attach storage; allocate a new FlowLauncherJsonStorage<Settings> (same type used
in the try) and call _settings.SetStorage(storage) after constructing the
fallback so the _settings instance has its storage set and subsequent saves/uses
won't hit null; reference LoadSettings, FlowLauncherJsonStorage<Settings>,
_settings and SetStorage when making the change.

Comment on lines +60 to +61
public void ShellRun(string cmd, string filename = "cmd.exe") =>
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = filename, Arguments = $"/c {cmd}", UseShellExecute = true });
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential command injection vulnerability in ShellRun.

The cmd parameter is passed directly to cmd.exe /c without escaping. Malicious input could execute arbitrary commands.

Proposed safer implementation
 public void ShellRun(string cmd, string filename = "cmd.exe") => 
-    System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = filename, Arguments = $"/c {cmd}", UseShellExecute = true });
+    System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo 
+    { 
+        FileName = filename, 
+        Arguments = filename == "cmd.exe" ? $"/c \"{cmd.Replace("\"", "\\\"")}\"" : cmd, 
+        UseShellExecute = true 
+    });

Note: The WPF implementation may have similar logic - consider aligning the behavior or documenting the security implications.

📝 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
public void ShellRun(string cmd, string filename = "cmd.exe") =>
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = filename, Arguments = $"/c {cmd}", UseShellExecute = true });
public void ShellRun(string cmd, string filename = "cmd.exe") =>
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = filename,
Arguments = filename == "cmd.exe" ? $"/c \"{cmd.Replace("\"", "\\\"")}\"" : cmd,
UseShellExecute = true
});
🤖 Prompt for AI Agents
In `@Flow.Launcher.Avalonia/AvaloniaPublicAPI.cs` around lines 60 - 61, ShellRun
currently passes the user-supplied cmd directly into "cmd.exe /c" which allows
command injection; update the ShellRun implementation in
AvaloniaPublicAPI.ShellRun to avoid invoking a shell with unescaped input:
either require callers to pass a specific executable and separate arguments
(split/validate the input) and call Process.Start with UseShellExecute = false
and FileName set to the executable and Arguments to a validated args string, or
implement strict validation/whitelisting of allowed commands when invoking
cmd.exe; ensure you do not concatenate raw cmd into "/c {cmd}" and mirror the
safer behavior in the WPF implementation or document the security tradeoffs.

Comment on lines +114 to +115
public T LoadSettingJsonStorage<T>() where T : new() => new T();
public void SaveSettingJsonStorage<T>() where T : new() { }
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

LoadSettingJsonStorage stub returns empty object, breaking plugin settings persistence.

Returning new T() means plugins won't load their persisted settings. This could cause significant issues for plugins that rely on saved configuration.

Consider delegating to the Infrastructure layer's JSON storage implementation or clearly documenting that settings persistence is not yet supported in Avalonia mode.

🤖 Prompt for AI Agents
In `@Flow.Launcher.Avalonia/AvaloniaPublicAPI.cs` around lines 114 - 115, The
current LoadSettingJsonStorage<T>() and SaveSettingJsonStorage<T>() stubs
return/perform nothing and break plugin settings persistence; update these
methods to delegate to the real JSON storage implementation in the
Infrastructure layer (call the Infrastructure JSON storage/load and save APIs
for type T) instead of returning new T(), and ensure SaveSettingJsonStorage<T>()
forwards the instance to be persisted (or, if no Infrastructure API exists yet,
throw a clear NotImplementedException and document that Avalonia mode does not
support settings persistence); use the method names LoadSettingJsonStorage<T>()
and SaveSettingJsonStorage<T>() as the integration points to wire to the
Infrastructure JSON storage.

Comment on lines +190 to +271
public static int Register(Modifiers modifiers, uint key, Action callback)
{
if (!_initialized)
{
Console.WriteLine("[GlobalHotkey] Not initialized");
return -1;
}

int id = _nextHotkeyId++;

// Add NoRepeat to prevent repeated triggers while held
uint mods = (uint)modifiers | (uint)Modifiers.NoRepeat;

if (!RegisterHotKey(_messageWindow, id, mods, key))
{
int error = Marshal.GetLastWin32Error();
Console.WriteLine($"[GlobalHotkey] Failed to register hotkey (error {error})");
return -1;
}

_hotkeyCallbacks[id] = callback;
Console.WriteLine($"[GlobalHotkey] Registered hotkey id={id}, mods={modifiers}, key=0x{key:X2}");
return id;
}

/// <summary>
/// Unregister a previously registered hotkey.
/// </summary>
public static void Unregister(int hotkeyId)
{
if (hotkeyId < 0 || _messageWindow == IntPtr.Zero) return;

UnregisterHotKey(_messageWindow, hotkeyId);
_hotkeyCallbacks.Remove(hotkeyId);
Console.WriteLine($"[GlobalHotkey] Unregistered hotkey id={hotkeyId}");
}

/// <summary>
/// Cleanup all hotkeys and resources.
/// </summary>
public static void Shutdown()
{
_messageTimer?.Stop();

foreach (var id in _hotkeyCallbacks.Keys)
{
UnregisterHotKey(_messageWindow, id);
}
_hotkeyCallbacks.Clear();

if (_messageWindow != IntPtr.Zero)
{
DestroyWindow(_messageWindow);
_messageWindow = IntPtr.Zero;
}

_initialized = false;
Console.WriteLine("[GlobalHotkey] Shutdown complete");
}

private static void ProcessMessages()
{
while (PeekMessage(out var msg, _messageWindow, 0, 0, 1)) // PM_REMOVE = 1
{
TranslateMessage(ref msg);
DispatchMessage(ref msg);
}
}

private static IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
if (msg == WM_HOTKEY)
{
int id = wParam.ToInt32();
if (_hotkeyCallbacks.TryGetValue(id, out var callback))
{
Console.WriteLine($"[GlobalHotkey] Hotkey triggered id={id}");
Dispatcher.UIThread.Post(() => callback());
}
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "GlobalHotkey.cs" | head -5

Repository: Flow-Launcher/Flow.Launcher

Length of output: 172


🏁 Script executed:

fd "GlobalHotkey.cs" --type f

Repository: Flow-Launcher/Flow.Launcher

Length of output: 168


🏁 Script executed:

cat -n ./Flow.Launcher.Avalonia/Helper/GlobalHotkey.cs

Repository: Flow-Launcher/Flow.Launcher

Length of output: 12130


🏁 Script executed:

# Check if there are any callers of Register/Unregister/Shutdown to see if they enforce UI thread
rg -A 5 "GlobalHotkey\.Register|GlobalHotkey\.Unregister|GlobalHotkey\.Shutdown" --type cs | head -50

Repository: Flow-Launcher/Flow.Launcher

Length of output: 1886


🏁 Script executed:

# Check HotKeyMapper.cs to see if Register/Unregister/Shutdown are called from UI thread
cat -n ./Flow.Launcher.Avalonia/Helper/HotKeyMapper.cs | head -100

Repository: Flow-Launcher/Flow.Launcher

Length of output: 3626


🏁 Script executed:

cat -n ./Flow.Launcher.Avalonia/Helper/HotKeyMapper.cs | tail -20

Repository: Flow-Launcher/Flow.Launcher

Length of output: 681


🏁 Script executed:

# Check if HotKeyMapper methods are annotated with UIThread or have explicit thread management
rg "UIThread|Dispatcher|Thread" ./Flow.Launcher.Avalonia/Helper/HotKeyMapper.cs

Repository: Flow-Launcher/Flow.Launcher

Length of output: 53


🏁 Script executed:

# Check where SetToggleHotkey and CheckAvailability are called
rg "SetToggleHotkey|CheckAvailability" --type cs -B 3 | head -60

Repository: Flow-Launcher/Flow.Launcher

Length of output: 3357


🏁 Script executed:

# Check if there's explicit STAThread or UI Thread requirement documented
rg "STAThread|UIThread|ThreadStatic" ./Flow.Launcher.Avalonia/Helper/GlobalHotkey.cs

Repository: Flow-Launcher/Flow.Launcher

Length of output: 130


Guard hotkey state with synchronization.

Register, Unregister, Shutdown, and WndProc access _hotkeyCallbacks and _nextHotkeyId without locking. The DispatcherTimer invokes ProcessMessages()WndProc() on the UI thread (which accesses the dictionary), while public static methods like Register and Unregister have no thread affinity enforcement and could be called from background threads. Concurrent access to the dictionary will cause race conditions and potential corruption.

🔒 Suggested fix: serialize access to hotkey state
@@
-    private static readonly Dictionary<int, Action> _hotkeyCallbacks = new();
-    private static int _nextHotkeyId = 1;
+    private static readonly Dictionary<int, Action> _hotkeyCallbacks = new();
+    private static int _nextHotkeyId = 1;
+    private static readonly object _hotkeyLock = new();
@@
-        int id = _nextHotkeyId++;
+        int id;
+        lock (_hotkeyLock)
+        {
+            id = _nextHotkeyId++;
+        }
@@
-        _hotkeyCallbacks[id] = callback;
+        lock (_hotkeyLock)
+        {
+            _hotkeyCallbacks[id] = callback;
+        }
@@
-        _hotkeyCallbacks.Remove(hotkeyId);
+        lock (_hotkeyLock)
+        {
+            _hotkeyCallbacks.Remove(hotkeyId);
+        }
@@
-        foreach (var id in _hotkeyCallbacks.Keys)
-        {
-            UnregisterHotKey(_messageWindow, id);
-        }
-        _hotkeyCallbacks.Clear();
+        List<int> ids;
+        lock (_hotkeyLock)
+        {
+            ids = new List<int>(_hotkeyCallbacks.Keys);
+            _hotkeyCallbacks.Clear();
+        }
+        foreach (var id in ids)
+        {
+            UnregisterHotKey(_messageWindow, id);
+        }
@@
-            if (_hotkeyCallbacks.TryGetValue(id, out var callback))
+            Action? callback;
+            lock (_hotkeyLock)
+            {
+                _hotkeyCallbacks.TryGetValue(id, out callback);
+            }
+            if (callback != null)
             {
                 Console.WriteLine($"[GlobalHotkey] Hotkey triggered id={id}");
-                Dispatcher.UIThread.Post(() => callback());
+                Dispatcher.UIThread.Post(callback);
             }
🤖 Prompt for AI Agents
In `@Flow.Launcher.Avalonia/Helper/GlobalHotkey.cs` around lines 190 - 271,
Concurrent access to _hotkeyCallbacks and _nextHotkeyId can corrupt state; add a
private static readonly object (e.g., _hotkeyLock) and use lock(_hotkeyLock) to
serialize all accesses and mutations: wrap the id generation and
_hotkeyCallbacks assignment in Register, the Remove call and UnregisterHotKey
call in Unregister, the iteration/unregister/clear sequence in Shutdown (or copy
keys to a list inside the lock then operate), and the lookup in WndProc (and any
other reads/writes like ProcessMessages if it touches the same state) so
Dictionary reads/writes and _nextHotkeyId increments are protected.

Comment on lines +45 to +74
public void Initialize()
{
try
{
// Add Flow Launcher language directory
AddFlowLauncherLanguageDirectory();

// Add plugin language directories
AddPluginLanguageDirectories();

// Load English as base/fallback
LoadLanguageFile(DefaultLanguageCode);

// Load the configured language on top if different from English
var languageCode = GetActualLanguageCode();
if (!string.Equals(languageCode, DefaultLanguageCode, StringComparison.OrdinalIgnoreCase))
{
LoadLanguageFile(languageCode);
}

// Update culture info
ChangeCultureInfo(languageCode);

Log.Info(ClassName, $"Loaded {_translations.Count} translations for language '{languageCode}'");
}
catch (Exception e)
{
Log.Exception(ClassName, "Failed to initialize internationalization", e);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Stop initialization when core language resources are missing.
If the core language directory or default English file is missing, continuing leaves _translations empty and the UI falls back to [key] placeholders. Return early after logging an error, and guard against an empty base load. Based on learnings: In Flow Launcher's Internationalization class, when the Flow Launcher language directory or default language file is missing, the only viable approach is to log an error and return early - there are no fallback mechanisms or alternative recovery strategies available due to architectural constraints.

🔧 Suggested fix
-            AddFlowLauncherLanguageDirectory();
+            if (!AddFlowLauncherLanguageDirectory())
+            {
+                return;
+            }
@@
-            LoadLanguageFile(DefaultLanguageCode);
+            LoadLanguageFile(DefaultLanguageCode);
+            if (_translations.Count == 0)
+            {
+                Log.Error(ClassName, $"Default language file '{DefaultLanguageCode}{Extension}' missing or empty.");
+                return;
+            }
@@
-    private void AddFlowLauncherLanguageDirectory()
+    private bool AddFlowLauncherLanguageDirectory()
     {
         var directory = Path.Combine(Constant.ProgramDirectory, LanguagesFolder);
         if (Directory.Exists(directory))
         {
             _languageDirectories.Add(directory);
             Log.Debug(ClassName, $"Added language directory: {directory}");
+            return true;
         }
-        else
-        {
-            Log.Warn(ClassName, $"Language directory not found: {directory}");
-        }
+        Log.Error(ClassName, $"Language directory not found: {directory}");
+        return false;
     }

Also applies to: 110-122

🤖 Prompt for AI Agents
In `@Flow.Launcher.Avalonia/Resource/Internationalization.cs` around lines 45 -
74, Initialize currently continues even if core language resources are missing,
leaving _translations empty and UI showing placeholders; modify Initialize to
stop and return early when AddFlowLauncherLanguageDirectory indicates the core
language directory is missing or when LoadLanguageFile(DefaultLanguageCode)
fails to populate translations: after calling AddFlowLauncherLanguageDirectory()
check its result (or verify the expected directory/file exists) and if missing
Log.Exception/Log.Error with context and return; likewise, after
LoadLanguageFile(DefaultLanguageCode) verify _translations is non-empty and if
empty Log.Error and return before attempting ChangeCultureInfo or loading a
secondary language; use the existing methods AddFlowLauncherLanguageDirectory,
LoadLanguageFile, DefaultLanguageCode, _translations and ChangeCultureInfo to
locate and implement these guards.

Comment on lines +24 to +27
public static System.Windows.Input.Key GetKeyFromVk(int vkCode)
{
return System.Windows.Input.KeyInterop.KeyFromVirtualKey(vkCode);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "==> Finding Flow.Launcher.Infrastructure.csproj"
csproj=$(fd -a 'Flow.Launcher.Infrastructure\.csproj$' 2>/dev/null | head -n 1)
if [ -z "$csproj" ]; then
  echo "Flow.Launcher.Infrastructure.csproj not found"
  exit 1
fi
echo "Found: $csproj"

echo ""
echo "==> Checking WPF/Windows references in $csproj"
cat "$csproj" | grep -i -E 'UseWPF|WindowsDesktop|PresentationCore|System\.Windows|TargetFramework' || echo "No matches found"

echo ""
echo "==> Checking all ItemGroup FrameworkReference and PackageReference entries"
cat "$csproj" | grep -A1 -E '<(Framework|Package)Reference' | head -30 || echo "No refs found"

echo ""
echo "==> GetKeyFromVk implementation and call sites"
rg -n 'GetKeyFromVk' -g '*.cs' -B2 -A5

echo ""
echo "==> Check if Infrastructure is referenced by Avalonia projects"
fd 'csproj$' | xargs grep -l 'Flow.Launcher.Infrastructure' 2>/dev/null | head -10

echo ""
echo "==> Check GlobalHotkey.cs usage in main projects"
rg -n 'GlobalHotkey' -g '*.cs' | grep -E '(using|new\s+GlobalHotkey)' | head -20

Repository: Flow-Launcher/Flow.Launcher

Length of output: 5442


Refactor: Move WPF-specific hotkey conversion out of shared Infrastructure.

GetKeyFromVk returns System.Windows.Input.Key, and Flow.Launcher.Avalonia directly consumes this method and compares the result against WPF Key enum values (lines 87, 92, 123, 126–129 in HotkeyRecorderDialog.axaml.cs). This couples Avalonia (a cross-platform UI framework) to WPF types. Since Infrastructure.csproj explicitly enables WPF (<UseWpf>true</UseWpf>) and includes WPF-specific dependencies (NHotkey.Wpf, SharpVectors.Wpf), move the hotkey key conversion helper to a WPF-only layer, or create a framework-neutral abstraction that returns a platform-agnostic enum or delegate to UI-framework-specific converters.

🤖 Prompt for AI Agents
In `@Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs` around lines 24 - 27,
GetKeyFromVk currently exposes WPF's System.Windows.Input.Key from the shared
Infrastructure, coupling Avalonia UI to WPF types; refactor by removing
GetKeyFromVk from Flow.Launcher.Infrastructure.Hotkey.GlobalHotkey and either
(a) move its implementation into a WPF-only assembly (e.g. the
Flow.Launcher.Avalonia/WPF project) and update HotkeyRecorderDialog.axaml.cs to
call the new WPF-local converter, or (b) introduce a framework-neutral
abstraction in Infrastructure (e.g. an enum HotkeyKey or an interface
IKeyConverter) that GlobalHotkey returns, and implement platform-specific
converters in WPF and Avalonia layers (use injection or a static
platform-specific converter) so HotkeyRecorderDialog.axaml.cs no longer depends
on System.Windows.Input.Key.

Comment on lines +319 to +327
else
{
// Fallback: show as normal window if owner is not available
dialog.Show();
var tcs = new TaskCompletionSource<bool?>();
dialog.Closed += (_, _) => tcs.TrySetResult(true);
await tcs.Task;
dialogResult = true;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find the file location
find . -type f -name "SettingsViewModel.cs" | grep -i explorer

Repository: Flow-Launcher/Flow.Launcher

Length of output: 142


🏁 Script executed:

# Find the ActionKeywordSetting dialog file
find . -type f -name "*ActionKeywordSetting*"

Repository: Flow-Launcher/Flow.Launcher

Length of output: 237


🏁 Script executed:

# Look at the EditActionKeywordAsync method in SettingsViewModel.cs
grep -n "EditActionKeywordAsync" . -r --include="*.cs"

Repository: Flow-Launcher/Flow.Launcher

Length of output: 208


🏁 Script executed:

# Read the EditActionKeywordAsync method in SettingsViewModel.cs
sed -n '304,360p' ./Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs

Repository: Flow-Launcher/Flow.Launcher

Length of output: 2103


🏁 Script executed:

# Read the ActionKeywordSetting dialog code
cat -n ./Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ActionKeywordSetting.axaml.cs

Repository: Flow-Launcher/Flow.Launcher

Length of output: 4782


🏁 Script executed:

# Check how Avalonia Window.Close() handles results in non-modal windows
# and verify if there's a DialogResult property or similar
rg -n "DialogResult|Close\(" ./Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ActionKeywordSetting.axaml.cs -A 2 -B 2

Repository: Flow-Launcher/Flow.Launcher

Length of output: 556


🏁 Script executed:

# Look for similar dialog patterns in the codebase to understand the intended approach
rg -n "ShowDialog|dialog\.Show\(\)" ./Plugins/Flow.Launcher.Plugin.Explorer/ --type cs -B 3 -A 5 | head -80

Repository: Flow-Launcher/Flow.Launcher

Length of output: 6023


🏁 Script executed:

# Check if there's a way to capture dialog result in Avalonia for non-modal windows
# by looking at how Window.Close is documented/used elsewhere
rg -n "Close\(true\)|Close\(false\)" ./Plugins/Flow.Launcher.Plugin.Explorer/ --type cs

Repository: Flow-Launcher/Flow.Launcher

Length of output: 756


🏁 Script executed:

# Check if ActionKeywordSetting or other dialogs expose a result property
rg -n "public.*Result|DialogResult" ./Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ --type cs

Repository: Flow-Launcher/Flow.Launcher

Length of output: 227


🏁 Script executed:

# Check how other Avalonia dialog methods handle non-modal window results
rg -n "\.Show\(\)" ./Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ --type cs -A 10 | grep -A 10 "Closed"

Repository: Flow-Launcher/Flow.Launcher

Length of output: 53


🏁 Script executed:

# Check if there's a way to store/expose the dialog result in ActionKeywordSetting
# by looking at the full file more carefully
cat -n ./Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ActionKeywordSetting.axaml.cs | grep -A 5 -B 5 "Close"

Repository: Flow-Launcher/Flow.Launcher

Length of output: 1293


🏁 Script executed:

# Check if Avalonia Window has a built-in way to capture Close() parameters
# by examining base Window class usage patterns in similar dialogs
rg -n "private.*Result|private.*bool|public.*bool.*Result" ./Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/QuickAccessLinkSettings.axaml.cs -A 2 -B 2

Repository: Flow-Launcher/Flow.Launcher

Length of output: 681


🏁 Script executed:

# Verify that the fallback pattern is indeed repeated with same issue in other methods
sed -n '439,452p' ./Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs

Repository: Flow-Launcher/Flow.Launcher

Length of output: 601


🏁 Script executed:

# Verify that Close() in Avalonia modal dialogs captures the parameter
# Check the actual behavior by looking at ShowDialog usage
rg -n "ShowDialog<bool" ./Plugins/Flow.Launcher.Plugin.Explorer/ --type cs -B 2 -A 2

Repository: Flow-Launcher/Flow.Launcher

Length of output: 1717


🏁 Script executed:

# Check if there are any other result-capture mechanisms in this file
sed -n '304,355p' ./Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs | grep -n "dialogResult\|return"

Repository: Flow-Launcher/Flow.Launcher

Length of output: 319


🏁 Script executed:

# Let me understand the actual behavior by checking how Avalonia Close() works
# Find if there's any documentation or other usage patterns
rg -n "\.Close\(" ./Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ --type cs -A 2 -B 2 | head -50

Repository: Flow-Launcher/Flow.Launcher

Length of output: 53


🏁 Script executed:

# Check the full EditActionKeywordAsync to see what happens after dialogResult is determined
sed -n '328,355p' ./Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs

Repository: Flow-Launcher/Flow.Launcher

Length of output: 1170


🏁 Script executed:

# Let me verify what happens with the dialog properties after Show()
# Check if ActionKeywordSetting stores any result state
rg -n "public|private" ./Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ActionKeywordSetting.axaml.cs | grep -E "Result|result"

Repository: Flow-Launcher/Flow.Launcher

Length of output: 53


🏁 Script executed:

# Understand the intended flow - what does OnDoneButtonClick actually validate?
sed -n '66,96p' ./Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ActionKeywordSetting.axaml.cs

Repository: Flow-Launcher/Flow.Launcher

Length of output: 1339


🏁 Script executed:

# Check if there's any property or state that could capture the Close parameter
rg -n "class ActionKeywordSetting" ./Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ActionKeywordSetting.axaml.cs -A 30 | head -40

Repository: Flow-Launcher/Flow.Launcher

Length of output: 990


🏁 Script executed:

# Verify the exact behavior - see what happens when dialog never closes (validation error)
# This would hang the fallback since tcs never completes
sed -n '66,96p' ./Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ActionKeywordSetting.axaml.cs | grep -n "Close\|ShowMsgBox"

Repository: Flow-Launcher/Flow.Launcher

Length of output: 448


🏁 Script executed:

# Check if there's a mechanism to capture the Close parameter in Avalonia
# by looking at Avalonia documentation patterns or similar usage
rg -n "TrySetResult\|Close\(" ./Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ --type cs | grep -B 2 "TrySetResult"

Repository: Flow-Launcher/Flow.Launcher

Length of output: 53


🏁 Script executed:

# Final check: look at what happens on line 95 when message is shown
# Does the dialog close or stay open?
sed -n '88,96p' ./Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ActionKeywordSetting.axaml.cs

Repository: Flow-Launcher/Flow.Launcher

Length of output: 375


🏁 Script executed:

# Check if there's a way to access the Close parameter value
# Look for any result storage mechanism in Avalonia window
rg -n "class ActionKeywordSetting" ./Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ActionKeywordSetting.axaml.cs -A 100 | grep -i "result\|bool\|close" | head -30

Repository: Flow-Launcher/Flow.Launcher

Length of output: 224


🏁 Script executed:

# Verify the issue by checking: when OnDoneButtonClick doesn't call Close(),
# what happens? Does the dialog close naturally?
# And check all paths in OnDoneButtonClick
sed -n '66,96p' ./Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ActionKeywordSetting.axaml.cs | grep -c "Close"

Repository: Flow-Launcher/Flow.Launcher

Length of output: 72


🏁 Script executed:

# Final verification: confirm the issue by checking if Avalonia Close() parameter
# is captured automatically for non-modal windows
# Look at Avalonia Window behavior documentation or similar patterns
rg -n "dialog\.Closed\|\.Closed\s*\+=" ./Plugins/Flow.Launcher.Plugin.Explorer/ --type cs -B 2 -A 3

Repository: Flow-Launcher/Flow.Launcher

Length of output: 53


🏁 Script executed:

# Check if this fallback pattern is truly the same across all 3 dialog methods
# by comparing lines 444-452 (second fallback)
sed -n '444,452p' ./Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs

Repository: Flow-Launcher/Flow.Launcher

Length of output: 420


Fix fallback dialog patterns that ignore user cancellation.

In EditActionKeywordAsync (and two other dialog methods), when ownerWindow is null, the fallback always sets dialogResult = true regardless of whether the user confirmed or cancelled the dialog. This causes keyword changes to apply even when the user cancelled, corrupting the action keyword configuration.

The dialog ShowDialog<bool?>() correctly captures the Close(true/false) parameter in the modal case, but the fallback Show() + Closed handler unconditionally assumes true.

Fix by either:

  • Adding a public result property to ActionKeywordSetting to store the Close() parameter before closing
  • Capturing the result in the Closed event handler instead of hardcoding true

This pattern appears 3 times in the file (methods around lines 304, 430, 456).

🤖 Prompt for AI Agents
In `@Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs` around
lines 319 - 327, The fallback path for modal dialogs in EditActionKeywordAsync
(and the two other dialog methods) incorrectly forces dialogResult = true after
dialog.Show(); instead capture the actual bool? result that the dialog returns:
add a temporary variable (or a public Result/Selected property on
ActionKeywordSetting) and, in the dialog.Closed handler, read that value (set by
the dialog before it closes) and call tcs.TrySetResult(result) instead of true;
then await tcs.Task and assign dialogResult from that captured value. Update the
three occurrences (the fallback Show()/Closed handlers) to use this pattern so
user cancellations produce false/null instead of always true.

Comment on lines +444 to +452
else
{
// Fallback: show as normal window if owner is not available
dialog.Show();
var tcs = new TaskCompletionSource<bool?>();
dialog.Closed += (_, _) => tcs.TrySetResult(true);
await tcs.Task;
dialogResult = true;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Same fallback issue: dialog result ignored.

Both EditQuickAccessLinkAsync and AddQuickAccessLinkAsync have the same fallback pattern that unconditionally sets dialogResult = true. This means Save() will be called even if the user cancelled the dialog when the owner window is unavailable.

Consider applying the same fix pattern as suggested for EditActionKeywordAsync, or extracting a helper method to handle the async dialog-with-fallback pattern consistently.

Also applies to: 470-478

🤖 Prompt for AI Agents
In `@Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs` around
lines 444 - 452, The fallback branch in EditQuickAccessLinkAsync and
AddQuickAccessLinkAsync unconditionally sets dialogResult = true; instead,
mirror the fix used in EditActionKeywordAsync (or extract a helper like
ShowDialogWithFallbackAsync(Window dialog)): create a
TaskCompletionSource<bool?>, wire dialog.Closed to
tcs.TrySetResult(dialog.DialogResult), await tcs.Task, and then set dialogResult
= (result == true) so Save() only runs when the dialog.result is true; update
both methods (and the similar block at the other location) to use this pattern
or call the shared helper.

Comment on lines 134 to +149
private async Task LoadImageAsync()
{
PreviewImage = await Main.Context.API.LoadImageAsync(FilePath, true).ConfigureAwait(false);
try
{
var imagePath = FilePath;
if (string.IsNullOrEmpty(imagePath) || !File.Exists(imagePath))
return;

var bitmap = new Bitmap(imagePath);
global::Avalonia.Threading.Dispatcher.UIThread.Post(() => PreviewImage = bitmap);
}
catch (Exception e)
{
Main.Context.API.LogException(ClassName, $"Failed to load image for {FilePath}", e);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find the PreviewPanel.axaml.cs file
find . -name "PreviewPanel.axaml.cs" -type f

Repository: Flow-Launcher/Flow.Launcher

Length of output: 200


🏁 Script executed:

# Look for context around the method to understand the full class
rg -A 20 -B 5 "LoadImageAsync" --type csharp | head -100

Repository: Flow-Launcher/Flow.Launcher

Length of output: 8482


🏁 Script executed:

# Check for other image loading patterns in the codebase
rg "new Bitmap\(" --type csharp -B 2 -A 5 | head -80

Repository: Flow-Launcher/Flow.Launcher

Length of output: 3482


🏁 Script executed:

# Read the complete PreviewPanel.axaml.cs file
cat -n "Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/PreviewPanel.axaml.cs"

Repository: Flow-Launcher/Flow.Launcher

Length of output: 17307


🏁 Script executed:

# Also check the other PreviewPanel to see if there are different patterns
cat -n "Flow.Launcher.Avalonia/Views/PreviewPanel.axaml.cs" | head -200

Repository: Flow-Launcher/Flow.Launcher

Length of output: 496


🏁 Script executed:

# Check how Avalonia's Bitmap is used in ImageLoader - particularly important for threading patterns
cat -n "Flow.Launcher.Avalonia/Helper/ImageLoader.cs" | head -150

Repository: Flow-Launcher/Flow.Launcher

Length of output: 6234


🏁 Script executed:

# Check how Dispatcher.UIThread is used for marshaling back to UI thread
rg "Dispatcher.UIThread\.(Post|InvokeAsync)" --type csharp -B 2 -A 2 | head -80

Repository: Flow-Launcher/Flow.Launcher

Length of output: 3419


🏁 Script executed:

# Check if there's any disposal pattern for previous bitmaps when setting PreviewImage
rg -A 5 "PreviewImage\s*=" "Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/PreviewPanel.axaml.cs"

Repository: Flow-Launcher/Flow.Launcher

Length of output: 319


🏁 Script executed:

# Check if Bitmap/IImage disposal is handled elsewhere
rg "IDisposable|Dispose\(\)" "Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/PreviewPanel.axaml.cs" -B 2 -A 2

Repository: Flow-Launcher/Flow.Launcher

Length of output: 53


🏁 Script executed:

# Check if PreviewImage property ever needs disposal when replaced
rg "PreviewImage" "Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/PreviewPanel.axaml.cs" -B 2 -A 2

Repository: Flow-Launcher/Flow.Launcher

Length of output: 329


🏁 Script executed:

# Search for any async/await patterns with Bitmap in the main Avalonia code
rg "Task.Run.*Bitmap|await.*Bitmap" --type csharp | head -20

Repository: Flow-Launcher/Flow.Launcher

Length of output: 53


Offload bitmap decoding to background thread to prevent UI freeze.

The new Bitmap(imagePath) constructor blocks the calling thread while decoding. For large images, this freezes the UI. Use Task.Run() to offload the operation, as shown in GetFolderSize() earlier in this file and in ImageLoader.cs throughout the codebase.

Suggested fix: offload bitmap decoding
 private async Task LoadImageAsync()
 {
     try
     {
         var imagePath = FilePath;
         if (string.IsNullOrEmpty(imagePath) || !File.Exists(imagePath))
             return;
 
-        var bitmap = new Bitmap(imagePath);
-        global::Avalonia.Threading.Dispatcher.UIThread.Post(() => PreviewImage = bitmap);
+        var bitmap = await Task.Run(() => new Bitmap(imagePath)).ConfigureAwait(false);
+        await global::Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() =>
+        {
+            (PreviewImage as IDisposable)?.Dispose();
+            PreviewImage = bitmap;
+        });
     }
     catch (Exception e)
     {
         Main.Context.API.LogException(ClassName, $"Failed to load image for {FilePath}", e);
     }
 }
🤖 Prompt for AI Agents
In `@Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/PreviewPanel.axaml.cs`
around lines 134 - 149, The Bitmap(imagePath) call in LoadImageAsync blocks the
UI thread; change LoadImageAsync to create/decode the Bitmap inside Task.Run
(off the UI thread) and then marshal the resulting Bitmap back onto the UI
thread via global::Avalonia.Threading.Dispatcher.UIThread.Post to set
PreviewImage; preserve existing exception handling (log via
Main.Context.API.LogException with ClassName and FilePath) and follow the same
pattern used by GetFolderSize() and ImageLoader.cs to avoid UI freezes when
loading large images.

Comment on lines +203 to +209
[RelayCommand]
private async Task Reindex()
{
IsIndexing = true;
await Main.IndexProgramsAsync();
IsIndexing = false;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Ensure IsIndexing resets on failure.
If IndexProgramsAsync throws, IsIndexing stays true and the UI may appear stuck.

Proposed fix
     private async Task Reindex()
     {
-        IsIndexing = true;
-        await Main.IndexProgramsAsync();
-        IsIndexing = false;
+        IsIndexing = true;
+        try
+        {
+            await Main.IndexProgramsAsync();
+        }
+        finally
+        {
+            IsIndexing = false;
+        }
     }
📝 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
[RelayCommand]
private async Task Reindex()
{
IsIndexing = true;
await Main.IndexProgramsAsync();
IsIndexing = false;
}
[RelayCommand]
private async Task Reindex()
{
IsIndexing = true;
try
{
await Main.IndexProgramsAsync();
}
finally
{
IsIndexing = false;
}
}
🤖 Prompt for AI Agents
In `@Plugins/Flow.Launcher.Plugin.Program/ViewModels/ProgramSettingViewModel.cs`
around lines 203 - 209, The Reindex method sets IsIndexing true but never resets
it if Main.IndexProgramsAsync throws; wrap the await Main.IndexProgramsAsync()
call in a try/finally (or try/catch/finally) inside the Reindex method so that
IsIndexing is set to false in the finally block, ensuring IsIndexing is always
reset regardless of exceptions thrown by Main.IndexProgramsAsync.

- Rename Avalonia-specific keys to match existing WPF keys in en.xaml:
  - proxyEnabled -> enableProxy, proxyServer -> server, etc.
  - toggleFlowLauncher -> flowlauncherHotkey
  - install -> installLocalPluginTooltip
  - plugin_open_plugin_directory -> pluginDirectory
- Add missing Avalonia-specific keys to en.xaml:
  - hotkeyFormatTip, sourceCode, loading
  - plugin_settings_open_in_wpf_window, openSettings
- Fix Explorer plugin settings to not expand general section by default
- Add DropdownDataGeneric<T> helper for translated enum dropdowns
- Fix GeneralSettingsViewModel to use DropdownDataGeneric for enums:
  - SearchWindowScreens, SearchWindowAligns, SearchPrecisionScore, LastQueryMode
- Fix ThemeSettingsViewModel to use DropdownDataGeneric for ColorSchemes
- Fix PluginsSettingsViewModel DisplayMode to use correct translation keys:
  - DisplayModeOnOff, DisplayModePriority, DisplayModeSearchDelay, DisplayModeHomeOnOff
- Add missing translation keys to en.xaml for plugin types and display modes
- Update XAML bindings to use DisplayMemberPath/SelectedValuePath pattern
…ling

- Add gold foreground color (#FFD700) for highlighted search matches
- Update HighlightTextConverter to use theme resource with fallback
- Fix TextBlockHelper to preserve Foreground property when copying Runs
- Fix highlight update bug: sync TitleHighlightData/SubTitleHighlightData/Score
  for reused ResultViewModel instances when query changes
- Update migration checklist to mark HighlightTextConverter as done
- Remove 13 codemap.md files from git index (Plugins/Flow.Launcher.Plugin.Explorer)
- Add **/codemap.md pattern to .gitignore
- Add .slim/ to .gitignore
Enable automatic GitHub Actions builds for pushes to the avalonia_migration branch.
Resolved conflicts:
- Calculator.csproj: Keep Mages 3.0.1 (from dev) + Avalonia packages
- Explorer SettingsViewModel.cs: Use PromptUserSelectFileAsync for ShellPath
- ExplorerSettings.xaml: Removed (deleted in HEAD)
- packages.lock.json: Restored from dev
@github-actions

This comment has been minimized.

Copy link
Contributor

@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: 9

🤖 Fix all issues with AI agents
In `@Flow.Launcher.Avalonia/ViewModel/ResultsViewModel.cs`:
- Around line 23-149: Add a partial method OnSelectedItemChanged to keep
SelectedIndex in sync when SelectedItem is changed by the UI: implement partial
void OnSelectedItemChanged(ResultViewModel? value) to set SelectedIndex = value
== null ? -1 : _results.IndexOf(value) (or -1 if not found), and ensure you
guard for _results being non-null and index bounds before changing
SelectedIndex; use the existing symbols SelectedItem, SelectedIndex,
OnSelectedItemChanged, and _results so the UI two-way binding stays consistent.

In `@Flow.Launcher.Avalonia/ViewModel/SettingPages/GeneralSettingsViewModel.cs`:
- Around line 40-43: SelectedLanguage can become null when _settings.Language
equals the special "system" code because LoadLanguages doesn't include a
matching Language entry; update LoadLanguages to insert a "System" Language
option (use the same system code constant that Internationalization expects)
into the Languages collection so Languages.FirstOrDefault(l => l.LanguageCode ==
_settings.Language) returns a non-null entry, and apply the same addition
wherever LoadLanguages is duplicated (see LoadLanguages and any similar
population code around the other occurrence) so the dropdown can represent and
re-select the system option.
- Around line 339-354: SelectPython and SelectNode are currently no-op commands;
implement a file picker in the SelectPython and SelectNode methods to let the
user choose the executable, assign the chosen path to
_settings.PluginSettings.PythonExecutablePath or
_settings.PluginSettings.NodeExecutablePath, persist settings if necessary, and
raise property change notifications by calling
OnPropertyChanged(nameof(PythonPath)) and OnPropertyChanged(nameof(NodePath));
alternatively, if you can't implement the picker now, disable or hide the
commands by adding a CanExecute check for the RelayCommand (or remove the
[RelayCommand] attribute and expose ICommand that returns false until supported)
so the buttons aren't clickable.

In `@Flow.Launcher.Avalonia/ViewModel/SettingPages/PluginsSettingsViewModel.cs`:
- Around line 54-61: The FilteredPlugins getter can throw when
PluginItemViewModel.Description or ActionKeywords/ActionKeywordsText are null;
update the predicate in FilteredPlugins (and the similar logic referenced by
PluginItemViewModel usage at the other spots) to null-safe checks—e.g., treat
null Description or ActionKeywordsText as empty strings and guard ActionKeywords
(or any collection) with ?., Any(), or null-coalescing before evaluating
Contains or joining into ActionKeywordsText—so the filter uses (Description ??
"") and (ActionKeywordsText ?? "") or checks ActionKeywords?.Any() appropriately
to avoid null reference exceptions.

In `@Flow.Launcher.Avalonia/ViewModel/SettingPages/ThemeSettingsViewModel.cs`:
- Around line 25-31: The ThemeSettingsViewModel currently subscribes to
_settings.PropertyChanged with a lambda that checks nameof(Settings.Language)
and calls UpdateLabels(), but never unsubscribes, causing a potential memory
leak; modify ThemeSettingsViewModel to either implement IDisposable and
unsubscribe the handler from _settings.PropertyChanged in Dispose, or use a weak
event pattern (WeakEventManager or equivalent) for the subscription so the
ViewModel can be garbage collected; ensure the subscription reference is stored
(e.g., a handler delegate) so it can be removed in Dispose if you choose
explicit unsubscription.

In `@Flow.Launcher.Avalonia/Views/SettingPages/PluginStoreSettingsPage.axaml`:
- Around line 18-29: Add accessible names to all icon-only Buttons by setting
AutomationProperties.Name to the localized tooltip string (use the same
i18n:Localize keys used for ToolTip.Tip) so screen readers announce them;
specifically update the Buttons bound to RefreshExternalPluginsCommand,
CheckPluginUpdatesCommand, InstallPluginCommand and likewise add
AutomationProperties.Name to the clear-filter Button (ClearFilterCommand) and
the website/source-code Buttons (OpenPluginWebsiteCommand /
OpenPluginSourceCommand or equivalent) using their existing i18n:Localize
resource keys.

In `@Flow.Launcher.Avalonia/Views/SettingPages/ProxySettingsPage.axaml`:
- Around line 28-32: The NumericUpDown inside SettingsExpander currently allows
a Minimum="0" which permits port 0; change the control's Minimum to "1" so
ProxyPort can only be set in the valid range 1–65535. Locate the NumericUpDown
in ProxySettingsPage.axaml (the element with Value="{Binding ProxyPort}" and
IsEnabled="{Binding ProxyEnabled}") and update its Minimum attribute from 0 to 1
while leaving Maximum="65535" and bindings unchanged.

In `@Flow.Launcher/Languages/en.xaml`:
- Line 229: Update the C# plugin label string value identified by the resource
key "pluginType_CSharp": change the text from "C# / .Net" to use the standard
Microsoft capitalization "C# / .NET" so the resource value for pluginType_CSharp
reads "C# / .NET".
- Around line 229-232: The non-English XAML resource files are missing nine new
keys (pluginType_CSharp, pluginType_Python, pluginType_Nodejs,
pluginType_Executable, hotkeyFormatTip, sourceCode, loading,
plugin_settings_open_in_wpf_window, openSettings), causing fallbacks or blank
UI; add these exact x:Key entries to each of the 25 non-English language files
(matching the keys used in Flow.Launcher/Languages/en.xaml) and provide
appropriate localized string values (or temporary English text as a fallback) so
the UI displays text for all cultures, ensuring keys and casing match exactly
across files.
🧹 Nitpick comments (10)
Flow.Launcher.Avalonia/Helper/TextBlockHelper.cs (1)

50-54: Non-Run inlines added directly may cause parent ownership issues.

Adding non-Run inline types directly without copying can cause problems since Avalonia inlines can only belong to one parent. If the source InlineCollection is reused (e.g., from a cached converter result), subsequent bindings will fail because the inline already has a parent.

Given the comment indicates this "may need enhancement," consider either:

  1. Implementing copy logic for other common inline types (e.g., Span, Bold, Italic)
  2. Logging a warning when encountering non-Run types to aid debugging
♻️ Suggested enhancement for robustness
                 else
                 {
-                    // For other inline types, add directly (may need enhancement)
-                    textBlock.Inlines?.Add(inline);
+                    // For other inline types, log and skip to avoid parent ownership issues
+                    // TODO: Implement copy logic for Span, Bold, Italic if needed
+                    System.Diagnostics.Debug.WriteLine($"TextBlockHelper: Unsupported inline type {inline.GetType().Name} encountered");
                 }
Flow.Launcher.Avalonia/ViewModel/SettingPages/ThemeSettingsViewModel.cs (3)

82-82: Repeated allocation on every property access.

MaxResultsRange creates a new List<int> each time it's accessed. Cache it as a static or instance field.

Proposed fix
+    private static readonly List<int> _maxResultsRange = Enumerable.Range(1, 20).ToList();
+
-    public List<int> MaxResultsRange => Enumerable.Range(1, 20).ToList();
+    public List<int> MaxResultsRange => _maxResultsRange;

97-115: Inconsistent setter pattern: missing equality checks.

QueryBoxFontSize and ResultItemFontSize setters don't check for equality before assigning, unlike the other properties in this ViewModel (MaxResults, UseGlyphIcons, SelectedColorScheme). This can cause unnecessary PropertyChanged notifications.

Proposed fix for consistency
     public double QueryBoxFontSize
     {
         get => _settings.QueryBoxFontSize;
         set
         {
+            if (_settings.QueryBoxFontSize != value)
+            {
                 _settings.QueryBoxFontSize = value;
                 OnPropertyChanged();
+            }
         }
     }

     public double ResultItemFontSize
     {
         get => _settings.ResultItemFontSize;
         set
         {
+            if (_settings.ResultItemFontSize != value)
+            {
                 _settings.ResultItemFontSize = value;
                 OnPropertyChanged();
+            }
         }
     }

56-67: Minor: Extra blank line before method body.

There's an unnecessary blank line between the method signature (line 56) and the opening brace (line 58).

AVALONIA_MIGRATION_CHECKLIST.md (4)

3-4: Update the "Last Updated" timestamp.

The timestamp shows "January 2026" but the current date is February 2026. Consider updating this to reflect the current migration status.


50-97: Consider fixing table formatting to satisfy markdown linting.

The tables throughout this document have several formatting issues flagged by markdownlint:

  1. Section headers embedded in tables (e.g., | **Startup Section** | on line 55) create column count mismatches because they span only 1 column instead of the expected 3 columns.
  2. Missing spaces around pipe separators in many table rows (MD060 violations).
  3. Bold text used as section headers within tables triggers MD036 warnings.

While the tables render correctly in most viewers, these issues may affect automated parsing and tooling.

Options to fix:

  • Convert embedded section headers to proper markdown headings (e.g., #### Startup Section) and restructure tables
  • Ensure section header rows span all columns: | **Startup Section** | | |
  • Add spaces around all pipe separators: | Setting | Status | Notes | instead of |Setting|Status|Notes|

Since this is a tracking document where readability is paramount, you may choose to suppress these linting rules if the current format is preferred.


412-467: Add language specifiers to code blocks.

The two file structure code blocks (lines 413-433 and 436-466) are missing language specifiers. Add text or plaintext to satisfy markdown linting and improve rendering:

📝 Suggested fix
 ### WPF Source Files
-```
+```text
 Flow.Launcher/
 ├── MainWindow.xaml (+ .cs)
 ...
 ### Avalonia Target Files
-```
+```text
 Flow.Launcher.Avalonia/
 ├── MainWindow.axaml (+ .cs)
 ...

50-254: Consider the maintenance burden of manual metrics.

The checklist includes many manually tracked metrics (line counts, percentages, progress ratios) throughout the document. These will require regular updates as the migration progresses and may become stale between updates.

Suggestions:

  • Consider adding a note encouraging contributors to update metrics when completing migration tasks
  • Alternatively, explore whether any metrics could be automatically generated via scripts (e.g., line counts from cloc or similar tools)
  • The "Last Updated" timestamp helps indicate freshness but should be consistently maintained

This is a common tradeoff for migration tracking documents where the value of having a roadmap outweighs the staleness risk.

Flow.Launcher.Avalonia/Views/SettingPages/GeneralSettingsPage.axaml (1)

154-197: Consider disabling dependent inputs when their toggles are off.

This avoids editing delay/home-page counts when the corresponding feature is disabled.

♻️ Suggested tweak
 <ui:NumberBox Value="{Binding SearchDelayTime}"
               Minimum="0"
               Maximum="1000"
               SmallChange="10"
               SpinButtonPlacementMode="Compact"
-              Width="120" />
+              Width="120"
+              IsEnabled="{Binding SearchQueryResultsWithDelay}" />
...
 <ui:NumberBox Value="{Binding MaxHistoryResultsToShow}"
               Minimum="0"
               Maximum="100"
               SmallChange="5"
               SpinButtonPlacementMode="Compact"
-              Width="120" />
+              Width="120"
+              IsEnabled="{Binding ShowHistoryResultsForHomePage}" />
Flow.Launcher.Avalonia/Views/SettingPages/PluginStoreSettingsPage.axaml (1)

77-79: Consider MinWidth/MinHeight for better DPI responsiveness.

Fixed Width/Height can clip text at higher scaling. Using MinWidth/MinHeight preserves the grid while allowing expansion.

♻️ Suggested tweak
-<Button Padding="0" 
-        Width="216" Height="184"
+<Button Padding="0" 
+        MinWidth="216" MinHeight="184"
         HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"
         Background="{DynamicResource ControlFillColorDefaultBrush}"
         BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
         BorderThickness="1"
         CornerRadius="{DynamicResource ControlCornerRadius}">

Comment on lines +23 to +149
[ObservableProperty]
private ResultViewModel? _selectedItem;

[ObservableProperty]
private int _selectedIndex;

[ObservableProperty]
private bool _isVisible = true;

/// <summary>
/// Sorted results collection bound to the UI.
/// Automatically sorted by Score descending.
/// </summary>
public ReadOnlyObservableCollection<ResultViewModel> Results => _results;

public Settings Settings => _settings;

public int MaxHeight => (int)(_settings.MaxResultsToShow * _settings.ItemHeightSize);

public ResultsViewModel(Settings settings)
{
_settings = settings;

// Connect SourceList to sorted ReadOnlyObservableCollection
_subscription = _sourceList.Connect()
.Sort(SortExpressionComparer<ResultViewModel>.Descending(r => r.Score))
.Bind(out _results)
.Subscribe();
}

/// <summary>
/// Replace all results with new ones using atomic Edit to prevent flickering.
/// Edit batches changes and fires only one notification at the end.
/// </summary>
public void ReplaceResults(IEnumerable<ResultViewModel> newResults)
{
var resultsList = newResults.ToList();
foreach (var r in resultsList)
{
r.Settings = _settings;
}

// Update highlight data and score for items that will be kept by EditDiff.
// This is necessary because EditDiff reuses existing ResultViewModel instances
// based on Title+SubTitle equality, but highlight indices are query-specific.
// For example, "Chrome" highlighted for "chr" [0,1,2] vs "chrome" [0,1,2,3,4,5].
var existingItems = _sourceList.Items.ToDictionary(r => (r.Title, r.SubTitle));
foreach (var newItem in resultsList)
{
if (existingItems.TryGetValue((newItem.Title, newItem.SubTitle), out var existing))
{
existing.TitleHighlightData = newItem.TitleHighlightData;
existing.SubTitleHighlightData = newItem.SubTitleHighlightData;
existing.Score = newItem.Score;
}
}

// EditDiff calculates minimal changes needed - items with same Title+SubTitle are kept
_sourceList.EditDiff(resultsList, ResultViewModelComparer.Instance);

// Select first item after replacement
if (_results.Count > 0)
{
SelectedIndex = 0;
SelectedItem = _results[0];
}
else
{
SelectedItem = null;
SelectedIndex = -1;
}
}

public void AddResult(ResultViewModel result)
{
result.Settings = _settings;
_sourceList.Add(result);

// Select first item if nothing selected
if (SelectedItem == null && _results.Count > 0)
{
SelectedIndex = 0;
SelectedItem = _results[0];
}
}

public void Clear()
{
_sourceList.Clear();
SelectedItem = null;
SelectedIndex = -1;
}

public void SelectNextItem()
{
if (_results.Count == 0) return;

var newIndex = SelectedIndex + 1;
if (newIndex >= _results.Count)
{
newIndex = 0; // Wrap to beginning
}

SelectedIndex = newIndex;
SelectedItem = _results[newIndex];
}

public void SelectPrevItem()
{
if (_results.Count == 0) return;

var newIndex = SelectedIndex - 1;
if (newIndex < 0)
{
newIndex = _results.Count - 1; // Wrap to end
}

SelectedIndex = newIndex;
SelectedItem = _results[newIndex];
}

partial void OnSelectedIndexChanged(int value)
{
if (value >= 0 && value < _results.Count)
{
SelectedItem = _results[value];
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Search for SelectedItem and SelectedIndex usage in AXAML and C# files
rg -n "SelectedItem|SelectedIndex" -g "*.axaml" -g "*.cs" -C 3 | head -100

Repository: Flow-Launcher/Flow.Launcher

Length of output: 12618


🏁 Script executed:

# Find AXAML files and search for ResultsViewModel binding
rg -l "ResultsViewModel" -g "*.axaml"

Repository: Flow-Launcher/Flow.Launcher

Length of output: 119


🏁 Script executed:

# Read the ResultListBox.axaml file
cat -n Flow.Launcher.Avalonia/Views/ResultListBox.axaml

Repository: Flow-Launcher/Flow.Launcher

Length of output: 7651


Add OnSelectedItemChanged to sync SelectedIndex when SelectedItem updates.

Both SelectedItem and SelectedIndex are bound with Mode=TwoWay in the UI (ResultListBox.axaml lines 19-20). The current code only syncs index → item via OnSelectedIndexChanged, but when the UI updates SelectedItem directly, SelectedIndex has no handler to update and can become stale.

♻️ Add bidirectional sync
    partial void OnSelectedIndexChanged(int value)
    {
        if (value >= 0 && value < _results.Count)
        {
            SelectedItem = _results[value];
        }
    }
+
+    partial void OnSelectedItemChanged(ResultViewModel? value)
+    {
+        var index = value == null ? -1 : _results.IndexOf(value);
+        if (index != SelectedIndex)
+            SelectedIndex = index;
+    }
🤖 Prompt for AI Agents
In `@Flow.Launcher.Avalonia/ViewModel/ResultsViewModel.cs` around lines 23 - 149,
Add a partial method OnSelectedItemChanged to keep SelectedIndex in sync when
SelectedItem is changed by the UI: implement partial void
OnSelectedItemChanged(ResultViewModel? value) to set SelectedIndex = value ==
null ? -1 : _results.IndexOf(value) (or -1 if not found), and ensure you guard
for _results being non-null and index bounds before changing SelectedIndex; use
the existing symbols SelectedItem, SelectedIndex, OnSelectedItemChanged, and
_results so the UI two-way binding stays consistent.

Comment on lines +40 to +43
public Language? SelectedLanguage
{
get => Languages.FirstOrDefault(l => l.LanguageCode == _settings.Language);
set
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add a "System" language option so the dropdown can represent existing settings.

If _settings.Language is the system code, SelectedLanguage resolves to null and users can’t re-select it. Include the system option (using the same code as Internationalization expects) in LoadLanguages.

🛠️ Suggested fix
 private void LoadLanguages()
 {
     // Minimal set of languages for now, can be expanded by loading from directory later
     Languages = new List<Language>
     {
+        new Language(Constant.SystemLanguageCode, "System"),
         new Language("en", "English"),
         new Language("zh-cn", "中文 (简体)"),
         new Language("zh-tw", "中文 (繁體)"),
         new Language("ko", "한국어"),
         new Language("ja", "日本語")
     };
 }

Also applies to: 360-370

🤖 Prompt for AI Agents
In `@Flow.Launcher.Avalonia/ViewModel/SettingPages/GeneralSettingsViewModel.cs`
around lines 40 - 43, SelectedLanguage can become null when _settings.Language
equals the special "system" code because LoadLanguages doesn't include a
matching Language entry; update LoadLanguages to insert a "System" Language
option (use the same system code constant that Internationalization expects)
into the Languages collection so Languages.FirstOrDefault(l => l.LanguageCode ==
_settings.Language) returns a non-null entry, and apply the same addition
wherever LoadLanguages is duplicated (see LoadLanguages and any similar
population code around the other occurrence) so the dropdown can represent and
re-select the system option.

Comment on lines +339 to +354
public string PythonPath => _settings.PluginSettings.PythonExecutablePath ?? "Not set";
public string NodePath => _settings.PluginSettings.NodeExecutablePath ?? "Not set";

[RelayCommand]
private async Task SelectPython()
{
// TODO: Implement file picker
await Task.CompletedTask;
}

[RelayCommand]
private async Task SelectNode()
{
// TODO: Implement file picker
await Task.CompletedTask;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

SelectPython/SelectNode are no-op commands right now.

The buttons will appear clickable but do nothing. Either implement the file picker (and update _settings.PluginSettings.* with OnPropertyChanged(nameof(PythonPath/NodePath))) or temporarily disable/hide the buttons until supported.

🤖 Prompt for AI Agents
In `@Flow.Launcher.Avalonia/ViewModel/SettingPages/GeneralSettingsViewModel.cs`
around lines 339 - 354, SelectPython and SelectNode are currently no-op
commands; implement a file picker in the SelectPython and SelectNode methods to
let the user choose the executable, assign the chosen path to
_settings.PluginSettings.PythonExecutablePath or
_settings.PluginSettings.NodeExecutablePath, persist settings if necessary, and
raise property change notifications by calling
OnPropertyChanged(nameof(PythonPath)) and OnPropertyChanged(nameof(NodePath));
alternatively, if you can't implement the picker now, disable or hide the
commands by adding a CanExecute check for the RelayCommand (or remove the
[RelayCommand] attribute and expose ICommand that returns false until supported)
so the buttons aren't clickable.

Comment on lines +54 to +61
public IEnumerable<PluginItemViewModel> FilteredPlugins =>
string.IsNullOrWhiteSpace(SearchText)
? Plugins
: Plugins.Where(p =>
p.Name.Contains(SearchText, StringComparison.OrdinalIgnoreCase) ||
p.Description.Contains(SearchText, StringComparison.OrdinalIgnoreCase) ||
p.ActionKeywordsText.Contains(SearchText, StringComparison.OrdinalIgnoreCase)
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard against null metadata in search and action keywords.
Third‑party plugins can omit fields, so Description or ActionKeywords being null would throw during filtering or ActionKeywordsText evaluation.

🛠️ Proposed fix
 public IEnumerable<PluginItemViewModel> FilteredPlugins => 
     string.IsNullOrWhiteSpace(SearchText) 
         ? Plugins 
         : Plugins.Where(p => 
-            p.Name.Contains(SearchText, StringComparison.OrdinalIgnoreCase) ||
-            p.Description.Contains(SearchText, StringComparison.OrdinalIgnoreCase) ||
-            p.ActionKeywordsText.Contains(SearchText, StringComparison.OrdinalIgnoreCase)
+            (p.Name?.Contains(SearchText, StringComparison.OrdinalIgnoreCase) ?? false) ||
+            (p.Description?.Contains(SearchText, StringComparison.OrdinalIgnoreCase) ?? false) ||
+            p.ActionKeywordsText.Contains(SearchText, StringComparison.OrdinalIgnoreCase)
         );
 
-public string ActionKeywordsText => string.Join(Query.ActionKeywordSeparator, _plugin.Metadata.ActionKeywords);
+public string ActionKeywordsText =>
+    string.Join(Query.ActionKeywordSeparator, _plugin.Metadata.ActionKeywords ?? Array.Empty<string>());

Also applies to: 314-315

🤖 Prompt for AI Agents
In `@Flow.Launcher.Avalonia/ViewModel/SettingPages/PluginsSettingsViewModel.cs`
around lines 54 - 61, The FilteredPlugins getter can throw when
PluginItemViewModel.Description or ActionKeywords/ActionKeywordsText are null;
update the predicate in FilteredPlugins (and the similar logic referenced by
PluginItemViewModel usage at the other spots) to null-safe checks—e.g., treat
null Description or ActionKeywordsText as empty strings and guard ActionKeywords
(or any collection) with ?., Any(), or null-coalescing before evaluating
Contains or joining into ActionKeywordsText—so the filter uses (Description ??
"") and (ActionKeywordsText ?? "") or checks ActionKeywords?.Any() appropriately
to avoid null reference exceptions.

Comment on lines +25 to +31
_settings.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(Settings.Language))
{
UpdateLabels();
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential memory leak from event subscription.

The PropertyChanged handler is subscribed but never unsubscribed. If the Settings instance outlives this ViewModel, the ViewModel won't be garbage collected. Consider implementing IDisposable or using weak event patterns.

Proposed fix using weak event or explicit disposal
-public partial class ThemeSettingsViewModel : ObservableObject
+public partial class ThemeSettingsViewModel : ObservableObject, IDisposable
 {
     private readonly Settings _settings;
     private readonly AvaloniaI18n _i18n;
+    private readonly PropertyChangedEventHandler _settingsChangedHandler;

     public ThemeSettingsViewModel()
     {
         _settings = Ioc.Default.GetRequiredService<Settings>();
         _i18n = Ioc.Default.GetRequiredService<AvaloniaI18n>();
         ColorSchemeOptions = DropdownDataGeneric<ColorSchemes>.GetEnumData("ColorScheme");

-        _settings.PropertyChanged += (s, e) =>
+        _settingsChangedHandler = (s, e) =>
         {
             if (e.PropertyName == nameof(Settings.Language))
             {
                 UpdateLabels();
             }
         };
+        _settings.PropertyChanged += _settingsChangedHandler;
     }
+
+    public void Dispose()
+    {
+        _settings.PropertyChanged -= _settingsChangedHandler;
+    }
🤖 Prompt for AI Agents
In `@Flow.Launcher.Avalonia/ViewModel/SettingPages/ThemeSettingsViewModel.cs`
around lines 25 - 31, The ThemeSettingsViewModel currently subscribes to
_settings.PropertyChanged with a lambda that checks nameof(Settings.Language)
and calls UpdateLabels(), but never unsubscribes, causing a potential memory
leak; modify ThemeSettingsViewModel to either implement IDisposable and
unsubscribe the handler from _settings.PropertyChanged in Dispose, or use a weak
event pattern (WeakEventManager or equivalent) for the subscription so the
ViewModel can be garbage collected; ensure the subscription reference is stored
(e.g., a handler delegate) so it can be removed in Dispose if you choose
explicit unsubscription.

Comment on lines +18 to +29
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="10">
<Button Command="{Binding RefreshExternalPluginsCommand}" ToolTip.Tip="{i18n:Localize refresh}">
<ui:SymbolIcon Symbol="Refresh" FontSize="16"/>
</Button>

<Button Command="{Binding CheckPluginUpdatesCommand}" ToolTip.Tip="{i18n:Localize checkUpdates}">
<ui:SymbolIcon Symbol="Sync" FontSize="16"/>
</Button>

<Button Command="{Binding InstallPluginCommand}" ToolTip.Tip="{i18n:Localize installLocalPluginTooltip}">
<ui:SymbolIcon Symbol="Add" FontSize="16"/>
</Button>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "PluginStoreSettingsPage.axaml" -type f

Repository: Flow-Launcher/Flow.Launcher

Length of output: 144


🏁 Script executed:

cat -n Flow.Launcher.Avalonia/Views/SettingPages/PluginStoreSettingsPage.axaml | head -150

Repository: Flow-Launcher/Flow.Launcher

Length of output: 11657


🏁 Script executed:

rg "AutomationProperties" --type xml --type xaml -i

Repository: Flow-Launcher/Flow.Launcher

Length of output: 99


🏁 Script executed:

rg "AutomationProperties" -i

Repository: Flow-Launcher/Flow.Launcher

Length of output: 1356


🌐 Web query:

Avalonia AutomationProperties Name accessible buttons screen readers

💡 Result:

In Avalonia, the “accessible name” that screen readers announce comes from the UI Automation (UIA) Name property. Avalonia exposes this via the attached property AutomationProperties.Name (it “affects the default value for GetName()”). [2]

What to do for accessible buttons

  • Text button: if the Button content is already meaningful text, you often don’t need anything extra (UIA commonly uses the visible text/content as the default name). If your button is icon-only or the content isn’t meaningful to a screen reader, set an explicit name. [5]

  • Icon-only button (recommended):

    <Button Content="{StaticResource IconSearch}"
            AutomationProperties.Name="Search" />

    (Keep it concise; don’t include words like “button” in the name—screen readers already know the role.) [5]

  • If there’s a visible label elsewhere, point to it with AutomationProperties.LabeledBy:

    <TextBlock x:Name="SaveLabel" Text="Save" />
    <Button Content="{StaticResource IconSave}"
            AutomationProperties.LabeledBy="{Binding `#SaveLabel`}" />

    (AutomationProperties.LabeledBy is an attached property in Avalonia.) [3]

  • Optional extra description: if a name alone isn’t enough, add AutomationProperties.HelpText. [4]

Where these properties come from in Avalonia

Avalonia defines these accessibility attached properties in Avalonia.Automation.AutomationProperties (including NameProperty, LabeledByProperty, and HelpTextProperty). [1]

Sources: Avalonia API docs for AutomationProperties and related attached properties [1][2][3][4], plus UIA guidance on using AutomationProperties.Name for icon-only buttons and naming rules [5].


Add accessible labels to icon-only buttons.

Icon-only buttons must set AutomationProperties.Name for screen reader support. Tooltips alone are not reliably announced by assistive technologies. Apply the fix to all icon-only buttons across the file: the refresh/sync/add buttons (lines 18-29), the clear filter button (lines 47-52), and the website/source code buttons (lines 116-125).

✅ Example fix (apply similarly to other icon-only buttons)
-<Button Command="{Binding RefreshExternalPluginsCommand}" ToolTip.Tip="{i18n:Localize refresh}">
+<Button Command="{Binding RefreshExternalPluginsCommand}"
+        ToolTip.Tip="{i18n:Localize refresh}"
+        AutomationProperties.Name="{i18n:Localize refresh}">
     <ui:SymbolIcon Symbol="Refresh" FontSize="16"/>
 </Button>
📝 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
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="10">
<Button Command="{Binding RefreshExternalPluginsCommand}" ToolTip.Tip="{i18n:Localize refresh}">
<ui:SymbolIcon Symbol="Refresh" FontSize="16"/>
</Button>
<Button Command="{Binding CheckPluginUpdatesCommand}" ToolTip.Tip="{i18n:Localize checkUpdates}">
<ui:SymbolIcon Symbol="Sync" FontSize="16"/>
</Button>
<Button Command="{Binding InstallPluginCommand}" ToolTip.Tip="{i18n:Localize installLocalPluginTooltip}">
<ui:SymbolIcon Symbol="Add" FontSize="16"/>
</Button>
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="10">
<Button Command="{Binding RefreshExternalPluginsCommand}"
ToolTip.Tip="{i18n:Localize refresh}"
AutomationProperties.Name="{i18n:Localize refresh}">
<ui:SymbolIcon Symbol="Refresh" FontSize="16"/>
</Button>
<Button Command="{Binding CheckPluginUpdatesCommand}"
ToolTip.Tip="{i18n:Localize checkUpdates}"
AutomationProperties.Name="{i18n:Localize checkUpdates}">
<ui:SymbolIcon Symbol="Sync" FontSize="16"/>
</Button>
<Button Command="{Binding InstallPluginCommand}"
ToolTip.Tip="{i18n:Localize installLocalPluginTooltip}"
AutomationProperties.Name="{i18n:Localize installLocalPluginTooltip}">
<ui:SymbolIcon Symbol="Add" FontSize="16"/>
</Button>
🤖 Prompt for AI Agents
In `@Flow.Launcher.Avalonia/Views/SettingPages/PluginStoreSettingsPage.axaml`
around lines 18 - 29, Add accessible names to all icon-only Buttons by setting
AutomationProperties.Name to the localized tooltip string (use the same
i18n:Localize keys used for ToolTip.Tip) so screen readers announce them;
specifically update the Buttons bound to RefreshExternalPluginsCommand,
CheckPluginUpdatesCommand, InstallPluginCommand and likewise add
AutomationProperties.Name to the clear-filter Button (ClearFilterCommand) and
the website/source-code Buttons (OpenPluginWebsiteCommand /
OpenPluginSourceCommand or equivalent) using their existing i18n:Localize
resource keys.

Comment on lines +28 to +32
<ui:SettingsExpander Header="{i18n:Localize port}">
<ui:SettingsExpander.Footer>
<NumericUpDown Value="{Binding ProxyPort}" Minimum="0" Maximum="65535" IsEnabled="{Binding ProxyEnabled}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Port minimum should be 1, not 0.

Port 0 is a reserved port that cannot be used as an actual proxy server port. Valid ports for proxy configuration are 1-65535.

Proposed fix
         <ui:SettingsExpander Header="{i18n:Localize port}">
             <ui:SettingsExpander.Footer>
-                <NumericUpDown Value="{Binding ProxyPort}" Minimum="0" Maximum="65535" IsEnabled="{Binding ProxyEnabled}" />
+                <NumericUpDown Value="{Binding ProxyPort}" Minimum="1" Maximum="65535" IsEnabled="{Binding ProxyEnabled}" />
             </ui:SettingsExpander.Footer>
         </ui:SettingsExpander>
📝 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
<ui:SettingsExpander Header="{i18n:Localize port}">
<ui:SettingsExpander.Footer>
<NumericUpDown Value="{Binding ProxyPort}" Minimum="0" Maximum="65535" IsEnabled="{Binding ProxyEnabled}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:SettingsExpander Header="{i18n:Localize port}">
<ui:SettingsExpander.Footer>
<NumericUpDown Value="{Binding ProxyPort}" Minimum="1" Maximum="65535" IsEnabled="{Binding ProxyEnabled}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
🤖 Prompt for AI Agents
In `@Flow.Launcher.Avalonia/Views/SettingPages/ProxySettingsPage.axaml` around
lines 28 - 32, The NumericUpDown inside SettingsExpander currently allows a
Minimum="0" which permits port 0; change the control's Minimum to "1" so
ProxyPort can only be set in the valid range 1–65535. Locate the NumericUpDown
in ProxySettingsPage.axaml (the element with Value="{Binding ProxyPort}" and
IsEnabled="{Binding ProxyEnabled}") and update its Minimum attribute from 0 to 1
while leaving Maximum="65535" and bindings unchanged.

<system:String x:Key="pluginStore_RecentlyUpdated">Recently Updated</system:String>
<system:String x:Key="pluginStore_None">Plugins</system:String>
<system:String x:Key="pluginStore_Installed">Installed</system:String>
<system:String x:Key="pluginType_CSharp">C# / .Net</system:String>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use standard “.NET” capitalization in the C# plugin label.

Minor text polish for consistency with Microsoft branding.

✏️ Suggested tweak
-    <system:String x:Key="pluginType_CSharp">C# / .Net</system:String>
+    <system:String x:Key="pluginType_CSharp">C# / .NET</system:String>
📝 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
<system:String x:Key="pluginType_CSharp">C# / .Net</system:String>
<system:String x:Key="pluginType_CSharp">C# / .NET</system:String>
🤖 Prompt for AI Agents
In `@Flow.Launcher/Languages/en.xaml` at line 229, Update the C# plugin label
string value identified by the resource key "pluginType_CSharp": change the text
from "C# / .Net" to use the standard Microsoft capitalization "C# / .NET" so the
resource value for pluginType_CSharp reads "C# / .NET".

Comment on lines +229 to +232
<system:String x:Key="pluginType_CSharp">C# / .Net</system:String>
<system:String x:Key="pluginType_Python">Python</system:String>
<system:String x:Key="pluginType_Nodejs">Node.js</system:String>
<system:String x:Key="pluginType_Executable">Executable</system:String>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check other language files for the newly added keys.
python - <<'PY'
import re, pathlib, sys

root = pathlib.Path(".")
lang_dir = next(root.glob("**/Flow.Launcher/Languages"), None)
if not lang_dir:
    print("Languages directory not found.")
    sys.exit(1)

new_keys = {
    "pluginType_CSharp",
    "pluginType_Python",
    "pluginType_Nodejs",
    "pluginType_Executable",
    "hotkeyFormatTip",
    "sourceCode",
    "loading",
    "plugin_settings_open_in_wpf_window",
    "openSettings",
}

def keys(path):
    text = path.read_text(encoding="utf-8", errors="ignore")
    return set(re.findall(r'x:Key="([^"]+)"', text))

en_keys = keys(lang_dir / "en.xaml")
print(f"en.xaml has {len(en_keys)} total keys")
print(f"Checking for {len(new_keys)} specific keys in other language files...\n")

for f in sorted(lang_dir.glob("*.xaml")):
    if f.name == "en.xaml":
        continue
    file_keys = keys(f)
    missing = sorted(new_keys - file_keys)
    if missing:
        print(f"{f.name}: missing {', '.join(missing)}")
    else:
        print(f"{f.name}: all keys present")
PY

Repository: Flow-Launcher/Flow.Launcher

Length of output: 4968


Add missing localization keys to all non-English language files.

All 9 new keys (pluginType_CSharp, pluginType_Python, pluginType_Nodejs, pluginType_Executable, hotkeyFormatTip, sourceCode, loading, plugin_settings_open_in_wpf_window, openSettings) are missing from all 25 non-English XAML files. Users will see fallback keys or blank text.

🤖 Prompt for AI Agents
In `@Flow.Launcher/Languages/en.xaml` around lines 229 - 232, The non-English XAML
resource files are missing nine new keys (pluginType_CSharp, pluginType_Python,
pluginType_Nodejs, pluginType_Executable, hotkeyFormatTip, sourceCode, loading,
plugin_settings_open_in_wpf_window, openSettings), causing fallbacks or blank
UI; add these exact x:Key entries to each of the 25 non-English language files
(matching the keys used in Flow.Launcher/Languages/en.xaml) and provide
appropriate localized string values (or temporary English text as a fallback) so
the UI displays text for all cultures, ensuring keys and casing match exactly
across files.

- Replace PluginManager.AllPlugins with GetAllLoadedPlugins()
- Replace PluginManager.NonGlobalPlugins with GetNonGlobalPlugins()
- Fix InitializePluginsAsync to pass IResultUpdateRegister parameter
- Fix QueryBuilder.Build to pass required 3 parameters
- Implement IResultUpdateRegister interface on MainViewModel
@github-actions

This comment has been minimized.

Copy link
Contributor

@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: 5

🤖 Fix all issues with AI agents
In `@Flow.Launcher.Avalonia/ViewModel/MainViewModel.cs`:
- Around line 267-269: The code cancels but does not dispose the previous
CancellationTokenSource (_queryTokenSource) in MainViewModel, which can leak
registrations; before reassigning _queryTokenSource create logic should call
_queryTokenSource.Cancel(); _queryTokenSource.Dispose(); (or safely Dispose if
not null) and then assign a new CancellationTokenSource so the old CTS is
properly cleaned up—update the block that currently uses
_queryTokenSource?.Cancel(); _queryTokenSource = new CancellationTokenSource();
var token = _queryTokenSource.Token; to cancel and dispose the existing
_queryTokenSource prior to creating the new one.
- Around line 193-202: The RegisterResultsUpdatedEvent stub must hook plugins
implementing IResultUpdated into the Avalonia result pipeline: detect if the
PluginPair (parameter name pair) has an IResultUpdated implementation, subscribe
its ResultsUpdated event to push updates into the existing
_resultsUpdateChannelWriter (write the event payload or a wrapper matching the
channel's expected type), and ensure you unsubscribe on plugin unload if/where
appropriate; implement this inside RegisterResultsUpdatedEvent so the Avalonia
pipeline receives incremental updates the same way the WPF implementation does.

In
`@Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj`:
- Around line 72-80: The CopyToAvaloniaOutput Target currently copies
PluginFiles but does not remove deleted/stale files from the AvaloniaPluginDir;
modify the CopyToAvaloniaOutput target (referencing Target Name
"CopyToAvaloniaOutput", PropertyGroup "AvaloniaPluginDir", ItemGroup
"PluginFiles" and the Copy task) to clean the destination before copying by
deleting or removing the $(AvaloniaPluginDir) contents (e.g., add a Delete or
RemoveDir step for $(AvaloniaPluginDir) and recreate the directory with MakeDir)
so only current build outputs are present after the target runs.

In `@Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs`:
- Around line 544-559: PromptUserSelectFolderAsync currently ignores the
initialDirectory parameter; convert the provided string into an IStorageFolder
and set it as SuggestedStartLocation on the FolderPickerOpenOptions so the
picker opens at the intended folder. Specifically, in
PromptUserSelectFolderAsync call Avalonia's
StorageProvider.TryGetFolderFromPathAsync(new Uri(path)) (guarding null/empty
initialDirectory and catching any Uri/lookup failures) and assign the resulting
IStorageFolder to SuggestedStartLocation on the FolderPickerOpenOptions; apply
the same pattern in PromptUserSelectFileAsync (use
FilePickerOpenOptions.SuggestedStartLocation) to preserve the existing UX when
an initialDirectory is provided.

In
`@Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj`:
- Around line 61-64: The project uses outdated package versions for Avalonia and
FluentAvaloniaUI; update the PackageReference entries named "Avalonia" and
"FluentAvaloniaUI" in Flow.Launcher.Plugin.WebSearch.csproj to at least
Version="11.3.11" for Avalonia and Version="2.5.0" for FluentAvaloniaUI, save
the .csproj, then run a restore/build to ensure the new packages resolve
correctly and update any binding/namespace changes if the compiler flags them.
🧹 Nitpick comments (3)
Flow.Launcher.Infrastructure/Logger/Log.cs (2)

1-1: Unused import.

System.Diagnostics does not appear to be used in this file. Demystify() comes from the Ben.Demystifier package, not from System.Diagnostics.

🧹 Suggested removal
-using System.Diagnostics;

148-150: Console output should be conditional and respect log level settings.

This console output runs unconditionally in all builds and bypasses the log level configured via SetLogLevel(). The existing debug target (lines 55-61) is wrapped in #if DEBUG for consistency. Consider applying the same pattern here to avoid unnecessary overhead in release builds and to maintain consistency.

♻️ Proposed fix
         logger.Log(level, message);
-
-        // Also output to console for easy debugging
-        System.Console.WriteLine($"[{level}] {classNameWithMethod}: {message}");
+#if DEBUG
+        // Also output to console for easy debugging
+        Console.WriteLine($"[{level}] {classNameWithMethod}: {message}");
+#endif
     }

You'll also need to add using System; at the top if not already implicitly available.

Plugins/Flow.Launcher.Plugin.Program/Main.cs (1)

440-445: Avoid Console.WriteLine in plugin settings creation.
Direct console output is noisy in GUI contexts; the structured logger already covers this.

♻️ Suggested tweak
-            System.Console.WriteLine("Program plugin: CreateSettingPanelAvalonia called!");
             Context.API.LogInfo(ClassName, "Creating Avalonia setting panel");

Comment on lines +193 to +202
/// <summary>
/// Register a plugin to receive results updated event.
/// Required by IResultUpdateRegister for plugin initialization.
/// </summary>
public void RegisterResultsUpdatedEvent(PluginPair pair)
{
// Avalonia uses a simplified result update model - plugins that implement
// IResultUpdated will have their events registered here when needed.
// For now, this is a stub as the basic query flow handles result updates.
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Search for IResultUpdated and IResultUpdateRegister usage in the codebase
rg -i "IResultUpdated|IResultUpdateRegister" --type cs

Repository: Flow-Launcher/Flow.Launcher

Length of output: 1465


🏁 Script executed:

# Get the RegisterResultsUpdatedEvent implementation from the original Flow.Launcher
rg -A 15 "public void RegisterResultsUpdatedEvent" Flow.Launcher/ViewModel/MainViewModel.cs

Repository: Flow-Launcher/Flow.Launcher

Length of output: 652


🏁 Script executed:

# Check the Avalonia implementation more carefully
cat -n Flow.Launcher.Avalonia/ViewModel/MainViewModel.cs | sed -n '190,210p'

Repository: Flow-Launcher/Flow.Launcher

Length of output: 945


🏁 Script executed:

# Check how PluginManager initializes plugins with the register
rg -B 5 -A 10 "InitializePluginsAsync" Flow.Launcher.Core/Plugin/PluginManager.cs

Repository: Flow-Launcher/Flow.Launcher

Length of output: 775


🏁 Script executed:

# Check if there are any other event subscriptions to ResultsUpdated in Avalonia
rg "ResultsUpdated" Flow.Launcher.Avalonia/

Repository: Flow-Launcher/Flow.Launcher

Length of output: 181


🏁 Script executed:

# Check what the basic query flow looks like in Avalonia
rg -B 5 -A 15 "QueryAsync|ResultCollection" Flow.Launcher.Avalonia/ViewModel/MainViewModel.cs | head -80

Repository: Flow-Launcher/Flow.Launcher

Length of output: 1528


🏁 Script executed:

# Check the full QueryAsync implementation in Avalonia to see if it handles incremental updates
rg -A 50 "private async Task QueryAsync" Flow.Launcher.Avalonia/ViewModel/MainViewModel.cs

Repository: Flow-Launcher/Flow.Launcher

Length of output: 1650


🏁 Script executed:

# Check if there's any result update handling or callbacks in Avalonia
rg "Results|result|Update" Flow.Launcher.Avalonia/ViewModel/MainViewModel.cs | grep -i "add\|insert\|update" | head -20

Repository: Flow-Launcher/Flow.Launcher

Length of output: 1360


🏁 Script executed:

# Check IResultUpdated interface to understand what events plugins expect
cat Flow.Launcher.Plugin/Interfaces/IResultUpdated.cs

Repository: Flow-Launcher/Flow.Launcher

Length of output: 1335


🏁 Script executed:

# Double-check if ResultsUpdated event subscription exists anywhere in Avalonia codebase
rg "ResultsUpdated\s*\+=" Flow.Launcher.Avalonia/

Repository: Flow-Launcher/Flow.Launcher

Length of output: 53


🏁 Script executed:

# Verify if ProcessResultUpdatesAsync or similar handles event-based updates
rg -B 5 -A 20 "ProcessResultUpdatesAsync" Flow.Launcher.Avalonia/ViewModel/MainViewModel.cs

Repository: Flow-Launcher/Flow.Launcher

Length of output: 2110


🏁 Script executed:

# Check if _resultsUpdateChannelWriter is written to elsewhere for plugin events
rg "_resultsUpdateChannelWriter" Flow.Launcher.Avalonia/ViewModel/MainViewModel.cs

Repository: Flow-Launcher/Flow.Launcher

Length of output: 432


🏁 Script executed:

# Verify the complete RegisterResultsUpdatedEvent implementation one more time
cat -n Flow.Launcher.Avalonia/ViewModel/MainViewModel.cs | sed -n '193,202p'

Repository: Flow-Launcher/Flow.Launcher

Length of output: 623


🏁 Script executed:

# Check if there's any plugin.ResultsUpdated subscription in Avalonia
rg "plugin\.ResultsUpdated|\.ResultsUpdated\s*\+=" Flow.Launcher.Avalonia/ViewModel/

Repository: Flow-Launcher/Flow.Launcher

Length of output: 53


IResultUpdated events are not wired into the result update pipeline.

The RegisterResultsUpdatedEvent method is a stub, so plugins implementing IResultUpdated cannot emit incremental result updates. While the Avalonia implementation has a channel-based result pipeline, it only handles initial results from parallel plugin queries—not event-based incremental updates from plugins like WebSearch.

Wire the plugin's ResultsUpdated event into the existing _resultsUpdateChannelWriter to match the WPF implementation, or remove IResultUpdateRegister until this feature is supported.

🤖 Prompt for AI Agents
In `@Flow.Launcher.Avalonia/ViewModel/MainViewModel.cs` around lines 193 - 202,
The RegisterResultsUpdatedEvent stub must hook plugins implementing
IResultUpdated into the Avalonia result pipeline: detect if the PluginPair
(parameter name pair) has an IResultUpdated implementation, subscribe its
ResultsUpdated event to push updates into the existing
_resultsUpdateChannelWriter (write the event payload or a wrapper matching the
channel's expected type), and ensure you unsubscribe on plugin unload if/where
appropriate; implement this inside RegisterResultsUpdatedEvent so the Avalonia
pipeline receives incremental updates the same way the WPF implementation does.

Comment on lines +267 to +269
_queryTokenSource?.Cancel();
_queryTokenSource = new CancellationTokenSource();
var token = _queryTokenSource.Token;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Dispose the previous CancellationTokenSource.
Cancelling without disposing can leak registrations over time during rapid query changes.

🛠️ Suggested fix
-        _queryTokenSource?.Cancel();
-        _queryTokenSource = new CancellationTokenSource();
+        _queryTokenSource?.Cancel();
+        _queryTokenSource?.Dispose();
+        _queryTokenSource = new CancellationTokenSource();
📝 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
_queryTokenSource?.Cancel();
_queryTokenSource = new CancellationTokenSource();
var token = _queryTokenSource.Token;
_queryTokenSource?.Cancel();
_queryTokenSource?.Dispose();
_queryTokenSource = new CancellationTokenSource();
var token = _queryTokenSource.Token;
🤖 Prompt for AI Agents
In `@Flow.Launcher.Avalonia/ViewModel/MainViewModel.cs` around lines 267 - 269,
The code cancels but does not dispose the previous CancellationTokenSource
(_queryTokenSource) in MainViewModel, which can leak registrations; before
reassigning _queryTokenSource create logic should call
_queryTokenSource.Cancel(); _queryTokenSource.Dispose(); (or safely Dispose if
not null) and then assign a new CancellationTokenSource so the old CTS is
properly cleaned up—update the block that currently uses
_queryTokenSource?.Cancel(); _queryTokenSource = new CancellationTokenSource();
var token = _queryTokenSource.Token; to cancel and dispose the existing
_queryTokenSource prior to creating the new one.

Comment on lines +72 to +80
<!-- Copy plugin output to Avalonia build directory -->
<Target Name="CopyToAvaloniaOutput" AfterTargets="Build">
<PropertyGroup>
<AvaloniaPluginDir>..\..\Output\$(Configuration)\Avalonia\Plugins\$(AssemblyName)\</AvaloniaPluginDir>
</PropertyGroup>
<ItemGroup>
<PluginFiles Include="$(OutputPath)**\*.*" />
</ItemGroup>
<Copy SourceFiles="@(PluginFiles)" DestinationFolder="$(AvaloniaPluginDir)%(RecursiveDir)" SkipUnchangedFiles="true" />
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Stale assemblies may linger in the Avalonia output directory.

Copy won’t remove files that were deleted from the build output, so old assemblies can remain and be loaded. Consider cleaning the destination before copying.

🧹 Suggested fix
   <Target Name="CopyToAvaloniaOutput" AfterTargets="Build">
     <PropertyGroup>
       <AvaloniaPluginDir>..\..\Output\$(Configuration)\Avalonia\Plugins\$(AssemblyName)\</AvaloniaPluginDir>
     </PropertyGroup>
     <ItemGroup>
       <PluginFiles Include="$(OutputPath)**\*.*" />
     </ItemGroup>
+    <RemoveDir Directories="$(AvaloniaPluginDir)" />
     <Copy SourceFiles="@(PluginFiles)" DestinationFolder="$(AvaloniaPluginDir)%(RecursiveDir)" SkipUnchangedFiles="true" />
   </Target>
📝 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
<!-- Copy plugin output to Avalonia build directory -->
<Target Name="CopyToAvaloniaOutput" AfterTargets="Build">
<PropertyGroup>
<AvaloniaPluginDir>..\..\Output\$(Configuration)\Avalonia\Plugins\$(AssemblyName)\</AvaloniaPluginDir>
</PropertyGroup>
<ItemGroup>
<PluginFiles Include="$(OutputPath)**\*.*" />
</ItemGroup>
<Copy SourceFiles="@(PluginFiles)" DestinationFolder="$(AvaloniaPluginDir)%(RecursiveDir)" SkipUnchangedFiles="true" />
<!-- Copy plugin output to Avalonia build directory -->
<Target Name="CopyToAvaloniaOutput" AfterTargets="Build">
<PropertyGroup>
<AvaloniaPluginDir>..\..\Output\$(Configuration)\Avalonia\Plugins\$(AssemblyName)\</AvaloniaPluginDir>
</PropertyGroup>
<ItemGroup>
<PluginFiles Include="$(OutputPath)**\*.*" />
</ItemGroup>
<RemoveDir Directories="$(AvaloniaPluginDir)" />
<Copy SourceFiles="@(PluginFiles)" DestinationFolder="$(AvaloniaPluginDir)%(RecursiveDir)" SkipUnchangedFiles="true" />
</Target>
🤖 Prompt for AI Agents
In
`@Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj`
around lines 72 - 80, The CopyToAvaloniaOutput Target currently copies
PluginFiles but does not remove deleted/stale files from the AvaloniaPluginDir;
modify the CopyToAvaloniaOutput target (referencing Target Name
"CopyToAvaloniaOutput", PropertyGroup "AvaloniaPluginDir", ItemGroup
"PluginFiles" and the Copy task) to clean the destination before copying by
deleting or removing the $(AvaloniaPluginDir) contents (e.g., add a Delete or
RemoveDir step for $(AvaloniaPluginDir) and recreate the directory with MakeDir)
so only current build outputs are present after the target runs.

Comment on lines +544 to 559
private static async Task<string?> PromptUserSelectFolderAsync(string? initialDirectory = null)
{
var mainWindow = AvaloniaApp.Current?.ApplicationLifetime is global::Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktop
? desktop.MainWindow
: null;

if (folderBrowserDialog.ShowDialog() != DialogResult.OK)
return path;
if (mainWindow == null) return null;

path = folderBrowserDialog.SelectedPath;
}
else if (type is ResultType.File)
var folders = await mainWindow.StorageProvider.OpenFolderPickerAsync(new global::Avalonia.Platform.Storage.FolderPickerOpenOptions
{
var openFileDialog = new OpenFileDialog();
if (initialDirectory is not null)
openFileDialog.InitialDirectory = initialDirectory;
AllowMultiple = false
});

if (openFileDialog.ShowDialog() != DialogResult.OK)
return path;
return folders.Count > 0 ? folders[0].Path.LocalPath : null;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Avalonia FolderPickerOpenOptions SuggestedStartLocation initial directory property

💡 Result:

In Avalonia, the “initial directory” for the folder picker is set via PickerOptions.SuggestedStartLocation (inherited by FolderPickerOpenOptions). It’s an IStorageFolder?, not a string path. [1][2]

Example (set start folder from a filesystem path):

var topLevel = TopLevel.GetTopLevel(this);

var start = await topLevel.StorageProvider
    .TryGetFolderFromPathAsync(new Uri(@"file:///C:/Projects"));

var folders = await topLevel.StorageProvider.OpenFolderPickerAsync(
    new FolderPickerOpenOptions
    {
        SuggestedStartLocation = start,
        AllowMultiple = false
    });

You can also use a well-known folder (when supported) via TryGetWellKnownFolderAsync(...). [1]

Notes:

  • This is only a suggestion; the OS/dialog may ignore it (missing access, folder doesn’t exist, some Linux pickers, etc.). [1]

Sources:
[1] Avalonia Docs – File Picker Options / SuggestedStartLocation
[2] Avalonia API Reference – PickerOptions.SuggestedStartLocation property


🏁 Script executed:

# First, let's locate and examine the file to verify the current state
fd "SettingsViewModel.cs" --type f

Repository: Flow-Launcher/Flow.Launcher

Length of output: 992


🏁 Script executed:

# Read the file and show the relevant sections
wc -l "Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs"

Repository: Flow-Launcher/Flow.Launcher

Length of output: 144


🏁 Script executed:

# Read lines 544-559
sed -n '544,559p' "Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs" | cat -n

Repository: Flow-Launcher/Flow.Launcher

Length of output: 872


🏁 Script executed:

# Read lines 560-573 to see the second occurrence
sed -n '560,573p' "Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs" | cat -n

Repository: Flow-Launcher/Flow.Launcher

Length of output: 835


🏁 Script executed:

# Also check the method signatures to see if both have initialDirectory parameter
sed -n '540,575p' "Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs" | cat -n

Repository: Flow-Launcher/Flow.Launcher

Length of output: 1740


Use SuggestedStartLocation in Avalonia picker options to honor initialDirectory.

Both PromptUserSelectFolderAsync() and PromptUserSelectFileAsync() accept an initialDirectory parameter that is never used. Avalonia's FolderPickerOpenOptions and FilePickerOpenOptions support SuggestedStartLocation (an IStorageFolder? property). Convert the string path to IStorageFolder via TryGetFolderFromPathAsync(new Uri(...)) and set it to preserve the existing UX.

🤖 Prompt for AI Agents
In `@Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs` around
lines 544 - 559, PromptUserSelectFolderAsync currently ignores the
initialDirectory parameter; convert the provided string into an IStorageFolder
and set it as SuggestedStartLocation on the FolderPickerOpenOptions so the
picker opens at the intended folder. Specifically, in
PromptUserSelectFolderAsync call Avalonia's
StorageProvider.TryGetFolderFromPathAsync(new Uri(path)) (guarding null/empty
initialDirectory and catching any Uri/lookup failures) and assign the resulting
IStorageFolder to SuggestedStartLocation on the FolderPickerOpenOptions; apply
the same pattern in PromptUserSelectFileAsync (use
FilePickerOpenOptions.SuggestedStartLocation) to preserve the existing UX when
an initialDirectory is provided.

Comment on lines +61 to +64
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.2.3" />
<PackageReference Include="FluentAvaloniaUI" Version="2.2.0" />
</ItemGroup>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check latest versions of Avalonia packages on NuGet

echo "Checking Avalonia version:"
curl -s "https://api.nuget.org/v3-flatcontainer/avalonia/index.json" | jq -r '.versions[-5:]'

echo ""
echo "Checking FluentAvaloniaUI version:"
curl -s "https://api.nuget.org/v3-flatcontainer/fluentavaloniaui/index.json" | jq -r '.versions[-5:]'

Repository: Flow-Launcher/Flow.Launcher

Length of output: 265


Update Avalonia and FluentAvaloniaUI package versions to current releases.

The current versions are outdated: Avalonia 11.2.3 (latest: 11.3.11) and FluentAvaloniaUI 2.2.0 (latest: 2.5.0). Update to at least FluentAvaloniaUI 2.5.0 and Avalonia 11.3.11 to receive bug fixes and improvements.

🤖 Prompt for AI Agents
In `@Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj`
around lines 61 - 64, The project uses outdated package versions for Avalonia
and FluentAvaloniaUI; update the PackageReference entries named "Avalonia" and
"FluentAvaloniaUI" in Flow.Launcher.Plugin.WebSearch.csproj to at least
Version="11.3.11" for Avalonia and Version="2.5.0" for FluentAvaloniaUI, save
the .csproj, then run a restore/build to ensure the new packages resolve
correctly and update any binding/namespace changes if the compiler flags them.

@github-actions

This comment has been minimized.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 115 files (changes from recent commits).

Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="AGENTS.md">

<violation number="1" location="AGENTS.md:166">
P3: The gotchas list says plugins run in separate AppDomains, but the code loads plugins via AssemblyLoadContext in the same process. This mismatch can mislead contributors about isolation behavior; update the doc to reflect the actual loading model.</violation>
</file>

<file name="Flow.Launcher.Avalonia/Flow.Launcher.Avalonia.csproj">

<violation number="1" location="Flow.Launcher.Avalonia/Flow.Launcher.Avalonia.csproj:84">
P2: The PR adds several post-build cleanup targets using PowerShell scripts to delete files (runtimes, XML docs, duplicate DLLs). This violates the principle of configuring the build properly instead of relying on post-build removal.

1. **Runtimes**: Instead of deleting `linux-x64` etc. manually, set `<RuntimeIdentifier>win-x64</RuntimeIdentifier>` in the `PropertyGroup`. This prevents NuGet from restoring/copying non-Windows assets.
2. **XML Docs**: Instead of deleting `.xml` files, use `<AllowedReferenceRelatedFileExtensions>.pdb</AllowedReferenceRelatedFileExtensions>` in the `PropertyGroup` to exclude XML documentation from dependencies.
3. **Duplicate DLLs**: Instead of deleting duplicates from `Plugins`, configure the referenced Plugin projects to set `<Private>false</Private>` (CopyLocal=false) for assemblies already provided by the host.

(Based on your team's feedback about avoiding post-build file removal; configure build properly.) [FEEDBACK_USED]</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@@ -0,0 +1,153 @@
<Project Sdk="Microsoft.NET.Sdk">
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 4, 2026

Choose a reason for hiding this comment

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

P2: The PR adds several post-build cleanup targets using PowerShell scripts to delete files (runtimes, XML docs, duplicate DLLs). This violates the principle of configuring the build properly instead of relying on post-build removal.

  1. Runtimes: Instead of deleting linux-x64 etc. manually, set <RuntimeIdentifier>win-x64</RuntimeIdentifier> in the PropertyGroup. This prevents NuGet from restoring/copying non-Windows assets.
  2. XML Docs: Instead of deleting .xml files, use <AllowedReferenceRelatedFileExtensions>.pdb</AllowedReferenceRelatedFileExtensions> in the PropertyGroup to exclude XML documentation from dependencies.
  3. Duplicate DLLs: Instead of deleting duplicates from Plugins, configure the referenced Plugin projects to set <Private>false</Private> (CopyLocal=false) for assemblies already provided by the host.

(Based on your team's feedback about avoiding post-build file removal; configure build properly.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Flow.Launcher.Avalonia/Flow.Launcher.Avalonia.csproj, line 84:

<comment>The PR adds several post-build cleanup targets using PowerShell scripts to delete files (runtimes, XML docs, duplicate DLLs). This violates the principle of configuring the build properly instead of relying on post-build removal.

1. **Runtimes**: Instead of deleting `linux-x64` etc. manually, set `<RuntimeIdentifier>win-x64</RuntimeIdentifier>` in the `PropertyGroup`. This prevents NuGet from restoring/copying non-Windows assets.
2. **XML Docs**: Instead of deleting `.xml` files, use `<AllowedReferenceRelatedFileExtensions>.pdb</AllowedReferenceRelatedFileExtensions>` in the `PropertyGroup` to exclude XML documentation from dependencies.
3. **Duplicate DLLs**: Instead of deleting duplicates from `Plugins`, configure the referenced Plugin projects to set `<Private>false</Private>` (CopyLocal=false) for assemblies already provided by the host.

(Based on your team's feedback about avoiding post-build file removal; configure build properly.) </comment>

<file context>
@@ -78,4 +79,75 @@
 
+  <!-- Remove duplicate DLLs from plugins that already exist in the root folder -->
+  <!-- Remove by name since root typically has newer compatible versions -->
+  <Target Name="RemoveDuplicatePluginDlls" AfterTargets="Build" Condition="Exists('$(OutputPath)Plugins')">
+    <Exec Command="powershell.exe -NoProfile -ExecutionPolicy Bypass -Command &quot;Get-ChildItem '$(OutputPath.TrimEnd('\'))' -Filter '*.dll' | ForEach-Object { $name = $_.Name; Get-ChildItem '$(OutputPath)Plugins' -Recurse -Filter $name | Remove-Item -Force }&quot;" IgnoreExitCode="true" />
+  </Target>
</file context>
Fix with Cubic


1. Build order matters - plugins must build before WPF
2. Build kills running `Flow.Launcher.exe` automatically
3. Plugins run in separate app domains
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 4, 2026

Choose a reason for hiding this comment

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

P3: The gotchas list says plugins run in separate AppDomains, but the code loads plugins via AssemblyLoadContext in the same process. This mismatch can mislead contributors about isolation behavior; update the doc to reflect the actual loading model.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At AGENTS.md, line 166:

<comment>The gotchas list says plugins run in separate AppDomains, but the code loads plugins via AssemblyLoadContext in the same process. This mismatch can mislead contributors about isolation behavior; update the doc to reflect the actual loading model.</comment>

<file context>
@@ -317,114 +145,31 @@ public class FuzzyMatcherTest
----
+1. Build order matters - plugins must build before WPF
+2. Build kills running `Flow.Launcher.exe` automatically
+3. Plugins run in separate app domains
+4. Settings auto-save via Fody PropertyChanged
+5. Some tests require Windows Search service (`WSearch`)
</file context>
Suggested change
3. Plugins run in separate app domains
3. Plugins are loaded via AssemblyLoadContext in the same process (no AppDomain isolation)
Fix with Cubic

Copy link
Contributor

@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: 8

🤖 Fix all issues with AI agents
In `@Flow.Launcher.Avalonia/AvaloniaPublicAPI.cs`:
- Around line 27-52: ChangeQuery currently sets _getMainViewModel().QueryText
unconditionally and ignores the requery flag; modify ChangeQuery to retrieve the
MainViewModel via _getMainViewModel(), compare the incoming query to
vm.QueryText, set vm.QueryText if different, and if requery is true and the text
is unchanged call the ViewModel method that re-executes the search (e.g.,
vm.Query() or the existing vm.ChangeQueryText/ChangeQuery method that honors
isReQuery) so plugins passing requery=true will force a re-execution; update
references to ChangeQuery in AvaloniaPublicAPI to use vm methods rather than
directly assigning QueryText when requery behavior is required.

In `@Flow.Launcher.Avalonia/Flow.Launcher.Avalonia.csproj`:
- Around line 98-101: The RemoveXmlDocFiles target is deleting project items
because the Delete task uses Files="@(None)" which matches source XML assets;
remove the Delete Files="@(None)" line from the Target RemoveXmlDocFiles and
keep only the output-folder cleanup Exec command (the PowerShell Get-ChildItem
... '$(OutputPath.TrimEnd('\'))' -Recurse -Filter '*.xml' | Remove-Item ...) so
that only built output XML files are removed and repo/source XML files are
preserved.
- Around line 34-46: The project references older package versions causing
missed updates/security patches; update the PackageReference entries in
Flow.Launcher.Avalonia.csproj for the listed packages: set Avalonia,
Avalonia.Desktop, Avalonia.Themes.Fluent and Avalonia.Fonts.Inter from 11.2.3 to
11.3.11, set FluentAvaloniaUI from 2.2.0 to 2.4.1, set DynamicData from 9.0.4 to
9.4.1, and set Microsoft.Extensions.DependencyInjection from 9.0.0 to 9.0.12
(leave the Debug-only Avalonia.Diagnostics condition as-is); modify the Version
attribute for each corresponding PackageReference element (e.g.,
PackageReference Include="Avalonia" Version="...") to the new version numbers
and restore/build to verify no compatibility issues.

In `@Flow.Launcher.Core/Plugin/JsonRPCV2Models/codemap.md`:
- Line 3: Replace the incorrect placeholder reference "Explorer" in the codemap
header comment with the correct component name for this file (e.g.,
"JsonRPCV2Models") or a neutral description; update the comment in codemap.md so
it accurately describes the architectural scope for the JsonRPCV2Models
directory (look for the string "Explorer" in codemap.md and change it to
"JsonRPCV2Models" or an appropriate description).

In `@Plugins/Flow.Launcher.Plugin.Shell/ViewModels/codemap.md`:
- Line 3: Replace the incorrect placeholder comment referencing "Explorer" in
the codemap.md file with an accurate reference to the Shell plugin ViewModels;
locate the HTML comment "<!-- Explorer: Fill in this section with architectural
understanding -->" and change its text to something like "<!-- Shell Plugin
ViewModels: Fill in this section with architectural understanding -->" (or
equivalent wording that correctly identifies the Shell plugin's ViewModels
directory).

In `@Scripts/compare_sizes.ps1`:
- Line 28: The difference formula on the Write-Host line is confusing because it
adds $wpfPlugins.Sum back into ($avTotal - $wpfTotal) even though $wpfTotal was
already computed excluding the Avalonia subfolder; update the expression to a
clear comparison (use ($avTotal - $wpfTotal) for "how much larger Avalonia is
than WPF") and add a brief comment above the Write-Host mentioning what totals
include/exclude, referencing the variables $avTotal, $wpfTotal, and
$wpfPlugins.Sum so future readers understand the intent.

In `@Scripts/plugin_runtimes.ps1`:
- Around line 4-5: The script fails if the Avalonia plugins path is missing
because Get-ChildItem on "Output/Release/Avalonia/Plugins" will throw; add a
Test-Path check for that directory before calling Get-ChildItem (guarding the
logic that sets $pluginRuntimes) and perform an early, non-failing exit (or
return/continue) when the path doesn't exist so the rest of the script can run
without hard failure.

In `@Scripts/size_analysis.ps1`:
- Around line 39-40: The log line claims the Avalonia subfolder is excluded but
calls Get-FolderSize('Output\Debug') which includes all subfolders; either
update the message or change the size calculation to exclude the Avalonia
folder. Fix options: 1) modify the Write-Host text ("excluding Avalonia
subfolder") to remove the exclusion claim, or 2) update the size computation by
calling a helper or filter that sums Get-ChildItem results for 'Output\Debug'
while excluding 'Output\Debug\Avalonia' (use the existing Get-FolderSize
function name as a reference) and then use that result in the Write-Host for the
WPF Debug total. Ensure the affected lines are the Write-Host calls that
reference "=== WPF Debug ===" and the Total using Get-FolderSize 'Output\Debug'.
🧹 Nitpick comments (11)
Scripts/plugin_runtimes.ps1 (1)

8-17: Make size/platform reporting resilient to empty or erroring folders.

Measure-Object can return $null (no files), and Get-ChildItem -Recurse can error on access issues. Coerce size to 0 and handle empty platforms for clearer output.

♻️ Proposed fix (resilient size/platforms)
-    $subdirs = Get-ChildItem $runtimeDir.FullName -Directory
-    $size = (Get-ChildItem $runtimeDir.FullName -Recurse -File | Measure-Object Length -Sum).Sum
+    $subdirs = Get-ChildItem $runtimeDir.FullName -Directory -ErrorAction SilentlyContinue
+    $size = (Get-ChildItem $runtimeDir.FullName -Recurse -File -ErrorAction SilentlyContinue |
+        Measure-Object Length -Sum).Sum
+    if (-not $size) { $size = 0 }
@@
-    Write-Host ("  Platforms: {0}" -f ($subdirs.Name -join ", "))
+    $platforms = if ($subdirs) { $subdirs.Name -join ", " } else { "None" }
+    Write-Host ("  Platforms: {0}" -f $platforms)
Scripts/plugin_analysis.ps1 (2)

5-6: Add existence checks for root directories.

Lines 5 and 6 access Output/Debug and Output/Debug/Avalonia without verifying they exist. If either path is missing (e.g., before first build), the script will throw errors. Consider wrapping with Test-Path checks for robustness.

Suggested fix
 # Get root DLLs for WPF and Avalonia
-$wpfRootDlls = Get-ChildItem "Output/Debug" -Filter "*.dll" | Select-Object -ExpandProperty Name
-$avRootDlls = Get-ChildItem "Output/Debug/Avalonia" -Filter "*.dll" | Select-Object -ExpandProperty Name
+$wpfRootDlls = @()
+$avRootDlls = @()
+if (Test-Path "Output/Debug") {
+    $wpfRootDlls = Get-ChildItem "Output/Debug" -Filter "*.dll" | Select-Object -ExpandProperty Name
+}
+if (Test-Path "Output/Debug/Avalonia") {
+    $avRootDlls = Get-ChildItem "Output/Debug/Avalonia" -Filter "*.dll" | Select-Object -ExpandProperty Name
+}

27-30: Missing existence check for Avalonia Plugins directory.

Line 13 checks Test-Path "Output/Debug/Avalonia/Plugins" before the duplicate calculation, but line 27 accesses the same path without a guard. For consistency and to avoid errors when the Plugins folder doesn't exist, wrap this section similarly.

Suggested fix
 # Show each plugin size in Avalonia
 Write-Host ""
 Write-Host "=== Avalonia plugin sizes ===" -ForegroundColor Yellow
-Get-ChildItem "Output/Debug/Avalonia/Plugins" -Directory | ForEach-Object {
-    $size = (Get-ChildItem $_.FullName -Recurse -File | Measure-Object Length -Sum).Sum
-    [PSCustomObject]@{ Name = $_.Name; SizeMB = [math]::Round($size / 1MB, 2) }
-} | Sort-Object SizeMB -Descending | Format-Table -AutoSize
+if (Test-Path "Output/Debug/Avalonia/Plugins") {
+    Get-ChildItem "Output/Debug/Avalonia/Plugins" -Directory | ForEach-Object {
+        $size = (Get-ChildItem $_.FullName -Recurse -File | Measure-Object Length -Sum).Sum
+        [PSCustomObject]@{ Name = $_.Name; SizeMB = [math]::Round($size / 1MB, 2) }
+    } | Sort-Object SizeMB -Descending | Format-Table -AutoSize
+} else {
+    Write-Host "Plugins folder not found"
+}
Scripts/show_remaining.ps1 (2)

4-8: Add existence checks before accessing directories.

The script assumes Output/Debug/Avalonia and Output/Debug/Avalonia/Plugins exist. If run before a build completes or in an incorrect working directory, it will fail with errors.

Suggested fix
 # Show remaining duplicates with details
 Write-Host "=== Remaining Duplicate DLLs in Avalonia Plugins ===" -ForegroundColor Cyan
 
+if (-not (Test-Path "Output/Debug/Avalonia")) {
+    Write-Host "Avalonia output not found. Run build first." -ForegroundColor Red
+    exit 1
+}
+
 $avRootDlls = Get-ChildItem "Output/Debug/Avalonia" -Filter "*.dll"
 
 $remaining = @()
+
+if (-not (Test-Path "Output/Debug/Avalonia/Plugins")) {
+    Write-Host "No Plugins folder found"
+    exit 0
+}

16-24: Handle potential null version values.

FileVersion can be null for some DLLs (e.g., native binaries without version resources). The comparison at line 24 would still work but may produce misleading results when both are null (showing SameVersion = True).

Suggested improvement
             # Check if version matches
-            $rootVersion = $matchingRoot.VersionInfo.FileVersion
-            $pluginVersion = $pluginDll.VersionInfo.FileVersion
+            $rootVersion = $matchingRoot.VersionInfo.FileVersion ?? "N/A"
+            $pluginVersion = $pluginDll.VersionInfo.FileVersion ?? "N/A"
             $remaining += [PSCustomObject]@{
                 Plugin = $pluginName
                 DLL = $pluginDll.Name
                 SizeMB = [math]::Round($pluginDll.Length / 1MB, 2)
                 RootVersion = $rootVersion
                 PluginVersion = $pluginVersion
-                SameVersion = ($rootVersion -eq $pluginVersion)
+                SameVersion = ($rootVersion -ne "N/A" -and $rootVersion -eq $pluginVersion)
             }
Scripts/find_duplicates.ps1 (2)

4-10: Add existence checks for robustness.

Similar to other scripts, this will fail if Output/Debug/Avalonia or Output/Debug/Avalonia/Plugins directories don't exist.

Suggested fix
 # Duplicate DLL analysis
 Write-Host "=== Duplicate DLLs in Avalonia Plugins ===" -ForegroundColor Cyan
 
+if (-not (Test-Path "Output/Debug/Avalonia")) {
+    Write-Host "Avalonia output directory not found" -ForegroundColor Red
+    exit 1
+}
+
 $avRootDlls = Get-ChildItem "Output/Debug/Avalonia" -Filter "*.dll"
 $avRootDllNames = $avRootDlls | Select-Object -ExpandProperty Name
 
 $totalDuplicateSize = 0
 $duplicatesByPlugin = @{}
 
+if (-not (Test-Path "Output/Debug/Avalonia/Plugins")) {
+    Write-Host "No Plugins folder to analyze"
+    exit 0
+}
+
 Get-ChildItem "Output/Debug/Avalonia/Plugins" -Directory | ForEach-Object {

36-40: Clarify the dedup calculation scope.

Line 37 calculates $currentSize from all files under Output/Debug/Avalonia recursively, which includes the Plugins subdirectory. The $reducedSize is correct since duplicates are only counted from within Plugins. Consider adding a comment to clarify this is intentional, or separate the metrics for clarity.

Suggested clarification
 # Also show the total current size vs potential reduced size
+# Note: currentSize includes all Avalonia files (root + plugins + runtimes)
+# Dedup savings come only from removing plugin DLLs that duplicate root DLLs
 $currentSize = (Get-ChildItem "Output/Debug/Avalonia" -Recurse -File | Measure-Object Length -Sum).Sum
 $reducedSize = $currentSize - $totalDuplicateSize
 Write-Host ("Current Avalonia size: {0:N2} MB" -f ($currentSize / 1MB))
 Write-Host ("After dedup: {0:N2} MB" -f ($reducedSize / 1MB))
Scripts/compare_sizes.ps1 (1)

3-7: Inconsistent error handling for missing directories.

Lines 3 and 6 access paths without -ErrorAction SilentlyContinue, while line 19 and 24 use it. For consistency, either add existence checks or use -ErrorAction SilentlyContinue throughout.

Suggested fix
 Write-Host "=== WPF Debug (excluding Avalonia) ===" -ForegroundColor Cyan
-$wpfRoot = Get-ChildItem "Output/Debug" -File | Measure-Object Length -Sum
+$wpfRoot = Get-ChildItem "Output/Debug" -File -ErrorAction SilentlyContinue | Measure-Object Length -Sum
 Write-Host ("Root files: {0:N2} MB" -f ($wpfRoot.Sum / 1MB))
 
-$wpfPlugins = Get-ChildItem "Output/Debug/Plugins" -Recurse -File | Measure-Object Length -Sum
+$wpfPlugins = Get-ChildItem "Output/Debug/Plugins" -Recurse -File -ErrorAction SilentlyContinue | Measure-Object Length -Sum
 Write-Host ("Plugins folder: {0:N2} MB" -f ($wpfPlugins.Sum / 1MB))
Scripts/release_sizes.ps1 (1)

12-18: Inconsistent error handling.

Lines 12 and 17 lack -ErrorAction SilentlyContinue while other lines include it. This inconsistency could cause errors if the WPF Release directory exists but lacks expected subfolders.

Suggested fix for consistency
 Write-Host ""
 Write-Host "=== Breakdown ===" -ForegroundColor Yellow
-$wpfRoot = (Get-ChildItem "Output/Release" -File | Measure-Object Length -Sum).Sum
+$wpfRoot = (Get-ChildItem "Output/Release" -File -ErrorAction SilentlyContinue | Measure-Object Length -Sum).Sum
 $avRoot = (Get-ChildItem "Output/Release/Avalonia" -File -ErrorAction SilentlyContinue | Measure-Object Length -Sum).Sum
 Write-Host ("WPF root files: {0:N2} MB" -f ($wpfRoot / 1MB))
 Write-Host ("Avalonia root files: {0:N2} MB" -f ($avRoot / 1MB))
 
-$wpfPlugins = (Get-ChildItem "Output/Release/Plugins" -Recurse -File | Measure-Object Length -Sum).Sum
+$wpfPlugins = (Get-ChildItem "Output/Release/Plugins" -Recurse -File -ErrorAction SilentlyContinue | Measure-Object Length -Sum).Sum
 $avPlugins = (Get-ChildItem "Output/Release/Avalonia/Plugins" -Recurse -File -ErrorAction SilentlyContinue | Measure-Object Length -Sum).Sum
Scripts/size_analysis.ps1 (1)

2-2: Remove unused parameter.

The $path parameter is declared but never used in the script. Either remove it or integrate it into the hardcoded paths below.

Suggested fix
 # Size analysis script
-param([string]$path = ".")
Flow.Launcher/packages.lock.json (1)

175-184: Avalonia 11.2.3 is safe, but consider upgrading to a newer stable version.

The lock file correctly reflects the addition of Avalonia 11.2.3 as a transitive dependency. No known security vulnerabilities are detected for this version. However, a newer stable version (Avalonia 11.3.11, released Jan 13, 2026) is available on NuGet. Consider upgrading to the latest version for access to bug fixes and improvements, or confirm that 11.2.3 is intentionally pinned for compatibility reasons.

Comment on lines +27 to +52
private readonly Settings _settings;
private readonly Func<MainViewModel> _getMainViewModel;
private readonly Internationalization _i18n;

public AvaloniaPublicAPI(Settings settings, Func<MainViewModel> getMainViewModel, Internationalization i18n)
{
_settings = settings;
_getMainViewModel = getMainViewModel;
_i18n = i18n;
}

#pragma warning disable CS0067
public event VisibilityChangedEventHandler? VisibilityChanged;
public event ActualApplicationThemeChangedEventHandler? ActualApplicationThemeChanged;
#pragma warning restore CS0067

// Essential for plugins
public void ChangeQuery(string query, bool requery = false) => _getMainViewModel().QueryText = query;

public string GetTranslation(string key) => _i18n.GetTranslation(key);

public List<PluginPair> GetAllPlugins() => PluginManager.GetAllLoadedPlugins();
public List<PluginPair> GetAllInitializedPlugins(bool includeFailed) => PluginManager.GetAllInitializedPlugins(includeFailed);
public MatchResult FuzzySearch(string query, string stringToCompare) =>
Ioc.Default.GetRequiredService<StringMatcher>().FuzzyMatch(query, stringToCompare);

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n "ChangeQuery\s*\(|ReQuery\s*\(|Requery" -g '*.cs' -A 3

Repository: Flow-Launcher/Flow.Launcher

Length of output: 14843


🏁 Script executed:

# Also search for QueryText property and how it's used
rg -n "QueryText\s*=" -g '*.cs' -A 2 -B 2

Repository: Flow-Launcher/Flow.Launcher

Length of output: 6533


🏁 Script executed:

# Look for any methods that might indicate requery intent
rg -n "class MainViewModel|QueryText|ExecuteQuery" -g '*.cs' -A 2

Repository: Flow-Launcher/Flow.Launcher

Length of output: 30993


Respect the requery flag in ChangeQuery.

The requery parameter is currently ignored, causing plugins that pass requery=true to fail to trigger re-execution when the query text remains unchanged. The WPF implementation correctly delegates this to ChangeQueryText, which checks isReQuery and calls Query() accordingly. Avalonia should implement similar logic or delegate to a VM method that respects the flag. Consider making ChangeQuery conditionally call a method that re-executes the query when requery=true and the text is unchanged.

🤖 Prompt for AI Agents
In `@Flow.Launcher.Avalonia/AvaloniaPublicAPI.cs` around lines 27 - 52,
ChangeQuery currently sets _getMainViewModel().QueryText unconditionally and
ignores the requery flag; modify ChangeQuery to retrieve the MainViewModel via
_getMainViewModel(), compare the incoming query to vm.QueryText, set
vm.QueryText if different, and if requery is true and the text is unchanged call
the ViewModel method that re-executes the search (e.g., vm.Query() or the
existing vm.ChangeQueryText/ChangeQuery method that honors isReQuery) so plugins
passing requery=true will force a re-execution; update references to ChangeQuery
in AvaloniaPublicAPI to use vm methods rather than directly assigning QueryText
when requery behavior is required.

@@ -0,0 +1,19 @@
# Flow.Launcher.Core/Plugin/JsonRPCV2Models/

<!-- Explorer: Fill in this section with architectural understanding -->
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix the placeholder comment reference.

The comment references "Explorer" but this codemap is for the JsonRPCV2Models directory.

📝 Proposed fix
-<!-- Explorer: Fill in this section with architectural understanding -->
+<!-- Fill in this section with architectural understanding -->
🤖 Prompt for AI Agents
In `@Flow.Launcher.Core/Plugin/JsonRPCV2Models/codemap.md` at line 3, Replace the
incorrect placeholder reference "Explorer" in the codemap header comment with
the correct component name for this file (e.g., "JsonRPCV2Models") or a neutral
description; update the comment in codemap.md so it accurately describes the
architectural scope for the JsonRPCV2Models directory (look for the string
"Explorer" in codemap.md and change it to "JsonRPCV2Models" or an appropriate
description).

@@ -0,0 +1,19 @@
# Plugins/Flow.Launcher.Plugin.Shell/ViewModels/

<!-- Explorer: Fill in this section with architectural understanding -->
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix the placeholder comment reference.

The comment references "Explorer" but this codemap is for the Shell plugin's ViewModels directory.

📝 Proposed fix
-<!-- Explorer: Fill in this section with architectural understanding -->
+<!-- Fill in this section with architectural understanding -->
📝 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
<!-- Explorer: Fill in this section with architectural understanding -->
<!-- Fill in this section with architectural understanding -->
🤖 Prompt for AI Agents
In `@Plugins/Flow.Launcher.Plugin.Shell/ViewModels/codemap.md` at line 3, Replace
the incorrect placeholder comment referencing "Explorer" in the codemap.md file
with an accurate reference to the Shell plugin ViewModels; locate the HTML
comment "<!-- Explorer: Fill in this section with architectural understanding
-->" and change its text to something like "<!-- Shell Plugin ViewModels: Fill
in this section with architectural understanding -->" (or equivalent wording
that correctly identifies the Shell plugin's ViewModels directory).

$avTotal = (Get-ChildItem "Output/Debug/Avalonia" -Recurse -File | Measure-Object Length -Sum).Sum
Write-Host ("WPF total (excl. Avalonia subfolder): {0:N2} MB" -f ($wpfTotal / 1MB))
Write-Host ("Avalonia total: {0:N2} MB" -f ($avTotal / 1MB))
Write-Host ("Difference: {0:N2} MB" -f (($avTotal - $wpfTotal + $wpfPlugins.Sum) / 1MB))
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Clarify or fix the difference calculation.

The formula ($avTotal - $wpfTotal + $wpfPlugins.Sum) is confusing. If the goal is to show how much larger Avalonia is than WPF, adding $wpfPlugins.Sum back seems incorrect since $wpfTotal already excludes the Avalonia subfolder per line 24. Consider clarifying the intent with a comment or simplifying to ($avTotal - $wpfTotal).

🤖 Prompt for AI Agents
In `@Scripts/compare_sizes.ps1` at line 28, The difference formula on the
Write-Host line is confusing because it adds $wpfPlugins.Sum back into ($avTotal
- $wpfTotal) even though $wpfTotal was already computed excluding the Avalonia
subfolder; update the expression to a clear comparison (use ($avTotal -
$wpfTotal) for "how much larger Avalonia is than WPF") and add a brief comment
above the Write-Host mentioning what totals include/exclude, referencing the
variables $avTotal, $wpfTotal, and $wpfPlugins.Sum so future readers understand
the intent.

Comment on lines +4 to +5
$pluginRuntimes = Get-ChildItem "Output/Release/Avalonia/Plugins" -Recurse -Directory -Filter "runtimes"

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard missing Avalonia plugins path to avoid hard failure.

If Output/Release/Avalonia/Plugins doesn’t exist (clean repo, Debug build, or Avalonia not built), Get-ChildItem throws and the script stops. Add a Test-Path early exit to keep this tool usable.

✅ Proposed fix (graceful exit)
-$pluginRuntimes = Get-ChildItem "Output/Release/Avalonia/Plugins" -Recurse -Directory -Filter "runtimes"
+$root = "Output/Release/Avalonia/Plugins"
+if (-not (Test-Path $root)) {
+    Write-Warning "Plugin runtime root not found: $root"
+    return
+}
+$pluginRuntimes = Get-ChildItem $root -Recurse -Directory -Filter "runtimes"
🤖 Prompt for AI Agents
In `@Scripts/plugin_runtimes.ps1` around lines 4 - 5, The script fails if the
Avalonia plugins path is missing because Get-ChildItem on
"Output/Release/Avalonia/Plugins" will throw; add a Test-Path check for that
directory before calling Get-ChildItem (guarding the logic that sets
$pluginRuntimes) and perform an early, non-failing exit (or return/continue)
when the path doesn't exist so the rest of the script can run without hard
failure.

Comment on lines +39 to +40
Write-Host "=== WPF Debug ===" -ForegroundColor Cyan
Write-Host "Total: $(Get-FolderSize 'Output\Debug') MB (excluding Avalonia subfolder)"
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Misleading comment: Avalonia subfolder is included.

The output claims "excluding Avalonia subfolder" but Get-FolderSize 'Output\Debug' recursively includes all files, including those under Output\Debug\Avalonia. Either fix the comment or adjust the logic to actually exclude the Avalonia subfolder.

Suggested fix to match the comment
 Write-Host "=== WPF Debug ===" -ForegroundColor Cyan
-Write-Host "Total: $(Get-FolderSize 'Output\Debug') MB (excluding Avalonia subfolder)"
+$wpfSize = if (Test-Path 'Output\Debug') {
+    $files = Get-ChildItem -Path 'Output\Debug' -Recurse -File -ErrorAction SilentlyContinue | 
+             Where-Object { $_.FullName -notmatch '\\Avalonia\\' }
+    [math]::Round(($files | Measure-Object -Property Length -Sum).Sum / 1MB, 2)
+} else { 0 }
+Write-Host "Total: $wpfSize MB (excluding Avalonia subfolder)"
📝 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
Write-Host "=== WPF Debug ===" -ForegroundColor Cyan
Write-Host "Total: $(Get-FolderSize 'Output\Debug') MB (excluding Avalonia subfolder)"
Write-Host "=== WPF Debug ===" -ForegroundColor Cyan
$wpfSize = if (Test-Path 'Output\Debug') {
$files = Get-ChildItem -Path 'Output\Debug' -Recurse -File -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -notmatch '\\Avalonia\\' }
[math]::Round(($files | Measure-Object -Property Length -Sum).Sum / 1MB, 2)
} else { 0 }
Write-Host "Total: $wpfSize MB (excluding Avalonia subfolder)"
🤖 Prompt for AI Agents
In `@Scripts/size_analysis.ps1` around lines 39 - 40, The log line claims the
Avalonia subfolder is excluded but calls Get-FolderSize('Output\Debug') which
includes all subfolders; either update the message or change the size
calculation to exclude the Avalonia folder. Fix options: 1) modify the
Write-Host text ("excluding Avalonia subfolder") to remove the exclusion claim,
or 2) update the size computation by calling a helper or filter that sums
Get-ChildItem results for 'Output\Debug' while excluding 'Output\Debug\Avalonia'
(use the existing Get-FolderSize function name as a reference) and then use that
result in the Write-Host for the WPF Debug total. Ensure the affected lines are
the Write-Host calls that reference "=== WPF Debug ===" and the Total using
Get-FolderSize 'Output\Debug'.

- Remove PowerShell-based duplicate DLL cleanup targets from Avalonia csproj
- Remove unnecessary runtime folder cleanup targets
- Update WPF packages.lock.json dependencies

These cleanup steps are now handled differently or are no longer needed.

💘 Generated with Crush

Assisted-by: Kimi for Coding via Crush <crush@charm.land>
@github-actions

This comment has been minimized.

… checklist

- Add PreviewHotkey property to HotkeySettingsViewModel
- Add preview hotkey UI to HotkeySettingsPage
- Implement dynamic hotkey handling in MainWindow code-behind
- Remove hardcoded F1 keybinding from XAML
- Update AVALONIA_MIGRATION_CHECKLIST.md with accurate progress:
  - Overall progress: 60-65% (was 35-40%)
  - Plugin Store: 95% done (was 0%)
  - Plugin Settings: 90% done (was 33%)
  - Proxy Settings: 100% done (was 59%)

💘 Generated with Crush

Assisted-by: Kimi for Coding via Crush <crush@charm.land>
@github-actions

This comment has been minimized.

Text highlighting was already implemented via HighlightTextConverter
and TitleHighlightData/SubTitleHighlightData in ResultViewModel.

💘 Generated with Crush

Assisted-by: Kimi for Coding via Crush <crush@charm.land>
@github-actions

This comment has been minimized.

Update all project files from net9.0 to net10.0:
- Core projects: net9.0-windows → net10.0-windows
- App projects: net9.0-windows10.0.19041.0 → net10.0-windows10.0.19041.0
- All plugin projects updated
- Microsoft.Extensions packages updated to 10.0.0-preview.1

Build verified with 1220 warnings (pre-existing), 0 errors.

💘 Generated with Crush

Assisted-by: Kimi for Coding via Crush <crush@charm.land>
Copy link
Contributor

@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: 3

🤖 Fix all issues with AI agents
In `@AVALONIA_MIGRATION_CHECKLIST.md`:
- Line 415: The TODO list still shows "HighlightTextConverter - Search match
highlighting" as high priority even though HighlightTextConverter has been
implemented; update the checklist entry by removing that line or marking it
checked (e.g., change the unchecked box to a checked box) so the item is no
longer listed as outstanding and the document reflects the implemented
HighlightTextConverter.
- Line 311: Update the checklist entry for HighlightTextConverter from ❌ Missing
to ✅ Done to match the implemented status; specifically change the table row
currently showing "HighlightTextConverter | :x: Missing | Bold gold highlighting
for matched characters" so it indicates completion (consistent with the "Text
highlighting" item at line 339 and the high-priority TODO at line 415 and the PR
commit message stating it was implemented via HighlightTextConverter).

In `@Flow.Launcher.Avalonia/MainWindow.axaml`:
- Around line 62-70: The SearchIconCanvas is intercepting pointer events so
clicks don't reach the query TextBox; update the SearchIconCanvas (and/or
SearchIcon) to be non-hit-testable by setting its IsHitTestVisible property to
false so pointer events pass through to the underlying query box (refer to the
SearchIconCanvas and SearchIcon elements to locate where to apply the change).
🧹 Nitpick comments (5)
Flow.Launcher.Avalonia/ViewModel/SettingPages/HotkeySettingsViewModel.cs (1)

13-16: Prefer constructor injection to avoid service-locator coupling.
Service-locator use makes testing harder and hides dependencies. Consider adding an injectable constructor and chaining from the parameterless one if XAML instantiation requires it.

♻️ Suggested refactor
-    public HotkeySettingsViewModel()
-    {
-        _settings = Ioc.Default.GetRequiredService<Settings>();
-    }
+    public HotkeySettingsViewModel()
+        : this(Ioc.Default.GetRequiredService<Settings>())
+    {
+    }
+
+    public HotkeySettingsViewModel(Settings settings)
+    {
+        _settings = settings ?? throw new ArgumentNullException(nameof(settings));
+    }
AVALONIA_MIGRATION_CHECKLIST.md (3)

440-461: Add language identifiers to fenced code blocks.

The markdown linter flags these code blocks for missing language specifiers. Adding text as the language identifier will resolve the warnings while maintaining the file tree formatting.

🔧 Suggested fix

For line 440:

-```
+```text
 Flow.Launcher/

For line 463:

-```
+```text
 Flow.Launcher.Avalonia/

Also applies to: 463-511


219-219: Verify Settings Progress calculation accuracy.

The stated "~1100/2609 lines (~42%)" doesn't match the sum of individual page line counts from the sections above:

  • General: ~270, Theme: ~70, Hotkey: ~26, Plugin: ~170, Store: ~200, Proxy: ~47, About: ~25
  • Sum: 808 lines (31% of 2609), not ~1100 (42%)

Please verify and update either the individual page estimates or the total calculation.


332-332: Add blank lines around tables for markdown compliance.

The markdown linter expects blank lines before and after tables. Adding these will resolve MD058 warnings.

📐 Example fix (line 332)
 ### Search & Results
+
 | Feature | Status | Notes |

Apply similar fixes to tables at lines 346, 356, and 365.

Also applies to: 346-346, 356-356, 365-365

Flow.Launcher.Avalonia/MainWindow.axaml.cs (1)

135-182: Consider implementing CanExecute conditions for command consistency.

Per the ICommand contract, RelayCommand.Execute() does not re-check CanExecute()—the caller is responsible. Currently, these four commands lack CanExecute guards and don't have conditional disable logic defined. While the implementations already include internal validation (e.g., LoadContextMenu() checks if a result exists), explicitly implementing CanExecute conditions would align with MVVM patterns and clarify intent when these commands become conditionally disabled in the future.

| BoolToIsVisibleConverter | :white_check_mark: Done | Boolean to IsVisible |
| TranslationConverter | :white_check_mark: Done | Localization converter |
| CommonConverters | :white_check_mark: Done | Various common converters |
| HighlightTextConverter | :x: Missing | Bold gold highlighting for matched characters |
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistency: HighlightTextConverter status conflicts with line 339 and commit message.

This converter is marked as ❌ Missing here, but line 339 marks "Text highlighting" as ✅ Done, and line 415 lists it as a high-priority TODO. The PR commit message explicitly states: "Marked text highlighting as done in checklist (implemented via HighlightTextConverter)."

Update this line to reflect the actual implementation status.

📝 Suggested fix
-| HighlightTextConverter | :x: Missing | Bold gold highlighting for matched characters |
+| HighlightTextConverter | :white_check_mark: Done | Bold gold highlighting for matched characters |
📝 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
| HighlightTextConverter | :x: Missing | Bold gold highlighting for matched characters |
| HighlightTextConverter | :white_check_mark: Done | Bold gold highlighting for matched characters |
🤖 Prompt for AI Agents
In `@AVALONIA_MIGRATION_CHECKLIST.md` at line 311, Update the checklist entry for
HighlightTextConverter from ❌ Missing to ✅ Done to match the implemented status;
specifically change the table row currently showing "HighlightTextConverter |
:x: Missing | Bold gold highlighting for matched characters" so it indicates
completion (consistent with the "Text highlighting" item at line 339 and the
high-priority TODO at line 415 and the PR commit message stating it was
implemented via HighlightTextConverter).

## Priority Recommendations

### High Priority (Core UX)
1. [ ] **HighlightTextConverter** - Search match highlighting
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove completed item from high priority list.

Per the PR commit message, HighlightTextConverter is already implemented. This item should be checked off or removed from the TODO list.

✅ Suggested fix
-1. [ ] **HighlightTextConverter** - Search match highlighting
+1. [x] **HighlightTextConverter** - Search match highlighting - DONE
📝 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
1. [ ] **HighlightTextConverter** - Search match highlighting
1. [x] **HighlightTextConverter** - Search match highlighting - DONE
🤖 Prompt for AI Agents
In `@AVALONIA_MIGRATION_CHECKLIST.md` at line 415, The TODO list still shows
"HighlightTextConverter - Search match highlighting" as high priority even
though HighlightTextConverter has been implemented; update the checklist entry
by removing that line or marking it checked (e.g., change the unchecked box to a
checked box) so the item is no longer listed as outstanding and the document
reflects the implemented HighlightTextConverter.

Comment on lines +62 to +70
<!-- Search Icon -->
<Canvas Name="SearchIconCanvas"
Classes="searchIconPosition"
HorizontalAlignment="Right">
<Path Name="SearchIcon"
Classes="searchIcon"
Data="{DynamicResource SearchIconGeometry}"
Stretch="Uniform" />
</Canvas>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Prevent the search icon overlay from stealing focus.

The icon canvas can intercept pointer events, so clicking the icon area won’t focus the query box. Making it non-hit-testable keeps click-to-focus behavior intact.

🧩 Suggested tweak
-                <Canvas Name="SearchIconCanvas" 
-                        Classes="searchIconPosition"
-                        HorizontalAlignment="Right">
+                <Canvas Name="SearchIconCanvas"
+                        Classes="searchIconPosition"
+                        HorizontalAlignment="Right"
+                        IsHitTestVisible="False">
Avalonia IsHitTestVisible default behavior for overlapping controls and passing pointer events through
🤖 Prompt for AI Agents
In `@Flow.Launcher.Avalonia/MainWindow.axaml` around lines 62 - 70, The
SearchIconCanvas is intercepting pointer events so clicks don't reach the query
TextBox; update the SearchIconCanvas (and/or SearchIcon) to be non-hit-testable
by setting its IsHitTestVisible property to false so pointer events pass through
to the underlying query box (refer to the SearchIconCanvas and SearchIcon
elements to locate where to apply the change).

@github-actions
Copy link

github-actions bot commented Feb 4, 2026

@check-spelling-bot Report

🔴 Please review

See the 📂 files view, the 📜action log, or 📝 job summary for details.

❌ Errors and Warnings Count
❌ check-file-path 187
❌ forbidden-pattern 1
⚠️ noisy-file 2
⚠️ non-alpha-in-dictionary 2

See ❌ Event descriptions for more information.

Forbidden patterns 🙅 (1)

In order to address this, you could change the content to not match the forbidden patterns (comments before forbidden patterns may help explain why they're forbidden), add patterns for acceptable instances, or adjust the forbidden patterns themselves.

These forbidden patterns matched content:

s.b. preexisting

[Pp]re[- ]existing
If the flagged items are 🤯 false positives

If items relate to a ...

  • binary file (or some other file you wouldn't want to check at all).

    Please add a file path to the excludes.txt file matching the containing file.

    File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.

    ^ refers to the file's path from the root of the repository, so ^README\.md$ would exclude README.md (on whichever branch you're using).

  • well-formed pattern.

    If you can write a pattern that would match it,
    try adding it to the patterns.txt file.

    Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines.

    Note that patterns can't match multiline strings.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 18 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="Flow.Launcher.Core/Flow.Launcher.Core.csproj">

<violation number="1" location="Flow.Launcher.Core/Flow.Launcher.Core.csproj:4">
P0: The project target framework is upgraded to `net10.0-windows`, but the PR description states the target is `.NET 9`. This discrepancy suggests an unintended change or outdated description.

Additionally:
1. The `Flow.Launcher.Avalonia` project is described as targeting .NET 9. A .NET 9 application cannot reference a .NET 10 library (`Core`).
2. The current build environment (and likely CI) only supports up to .NET 9.0 (verified via `dotnet --list-sdks`), so this change will cause build failures.
3. Bundling a major .NET version upgrade with the Avalonia migration increases risk and scope.

Recommended: Revert to `net9.0-windows` to match the PR description and ensure compatibility.</violation>
</file>

<file name="Flow.Launcher/Flow.Launcher.csproj">

<violation number="1" location="Flow.Launcher/Flow.Launcher.csproj:5">
P2: This upgrade to .NET 10 directly contradicts the PR description, which states the project targets .NET 9. Additionally, upgrading the stable WPF application to a new major version increases the scope and risk of this migration PR.

If the upgrade is intentional, update the PR description to reflect this change. Otherwise, revert to `net9.0-windows10.0.19041.0`.</violation>

<violation number="2" location="Flow.Launcher/Flow.Launcher.csproj:147">
P1: Usage of `10.0.0-preview.1` packages is unsafe for production. In Feb 2026, .NET 10 has been stable since Nov 2025. These preview packages are approximately one year old and should be replaced with stable versions (e.g., `10.0.0`) to ensure security and stability.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.


<PropertyGroup>
<TargetFramework>net9.0-windows</TargetFramework>
<TargetFramework>net10.0-windows</TargetFramework>
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 4, 2026

Choose a reason for hiding this comment

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

P0: The project target framework is upgraded to net10.0-windows, but the PR description states the target is .NET 9. This discrepancy suggests an unintended change or outdated description.

Additionally:

  1. The Flow.Launcher.Avalonia project is described as targeting .NET 9. A .NET 9 application cannot reference a .NET 10 library (Core).
  2. The current build environment (and likely CI) only supports up to .NET 9.0 (verified via dotnet --list-sdks), so this change will cause build failures.
  3. Bundling a major .NET version upgrade with the Avalonia migration increases risk and scope.

Recommended: Revert to net9.0-windows to match the PR description and ensure compatibility.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Flow.Launcher.Core/Flow.Launcher.Core.csproj, line 4:

<comment>The project target framework is upgraded to `net10.0-windows`, but the PR description states the target is `.NET 9`. This discrepancy suggests an unintended change or outdated description.

Additionally:
1. The `Flow.Launcher.Avalonia` project is described as targeting .NET 9. A .NET 9 application cannot reference a .NET 10 library (`Core`).
2. The current build environment (and likely CI) only supports up to .NET 9.0 (verified via `dotnet --list-sdks`), so this change will cause build failures.
3. Bundling a major .NET version upgrade with the Avalonia migration increases risk and scope.

Recommended: Revert to `net9.0-windows` to match the PR description and ensure compatibility.</comment>

<file context>
@@ -1,7 +1,7 @@
 
   <PropertyGroup>
-    <TargetFramework>net9.0-windows</TargetFramework>
+    <TargetFramework>net10.0-windows</TargetFramework>
     <UseWpf>true</UseWpf>
     <UseWindowsForms>true</UseWindowsForms>
</file context>
Suggested change
<TargetFramework>net10.0-windows</TargetFramework>
<TargetFramework>net9.0-windows</TargetFramework>
Fix with Cubic

<PackageReference Include="MdXaml.Svg" Version="1.27.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.9" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.9" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0-preview.1.25080.5" />
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 4, 2026

Choose a reason for hiding this comment

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

P1: Usage of 10.0.0-preview.1 packages is unsafe for production. In Feb 2026, .NET 10 has been stable since Nov 2025. These preview packages are approximately one year old and should be replaced with stable versions (e.g., 10.0.0) to ensure security and stability.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Flow.Launcher/Flow.Launcher.csproj, line 147:

<comment>Usage of `10.0.0-preview.1` packages is unsafe for production. In Feb 2026, .NET 10 has been stable since Nov 2025. These preview packages are approximately one year old and should be replaced with stable versions (e.g., `10.0.0`) to ensure security and stability.</comment>

<file context>
@@ -144,8 +144,8 @@
     <PackageReference Include="MdXaml.Svg" Version="1.27.0" />
-    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.9" />
-    <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.9" />
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0-preview.1.25080.5" />
+    <PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0-preview.1.25080.5" />
     <PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
</file context>
Fix with Cubic

<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows10.0.19041.0</TargetFramework>
<TargetFramework>net10.0-windows10.0.19041.0</TargetFramework>
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 4, 2026

Choose a reason for hiding this comment

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

P2: This upgrade to .NET 10 directly contradicts the PR description, which states the project targets .NET 9. Additionally, upgrading the stable WPF application to a new major version increases the scope and risk of this migration PR.

If the upgrade is intentional, update the PR description to reflect this change. Otherwise, revert to net9.0-windows10.0.19041.0.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Flow.Launcher/Flow.Launcher.csproj, line 5:

<comment>This upgrade to .NET 10 directly contradicts the PR description, which states the project targets .NET 9. Additionally, upgrading the stable WPF application to a new major version increases the scope and risk of this migration PR.

If the upgrade is intentional, update the PR description to reflect this change. Otherwise, revert to `net9.0-windows10.0.19041.0`.</comment>

<file context>
@@ -2,7 +2,7 @@
   <PropertyGroup>
     <OutputType>WinExe</OutputType>
-    <TargetFramework>net9.0-windows10.0.19041.0</TargetFramework>
+    <TargetFramework>net10.0-windows10.0.19041.0</TargetFramework>
     <UseWPF>true</UseWPF>
     <UseWindowsForms>false</UseWindowsForms>
</file context>
Fix with Cubic

Copy link
Contributor

@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: 4

🤖 Fix all issues with AI agents
In `@Flow.Launcher.Avalonia/Flow.Launcher.Avalonia.csproj`:
- Around line 20-28: The PropertyGroup entries currently replace the
DefineConstants value; update both Debug and Release PropertyGroup blocks so
they append SDK/product constants instead of overriding by adding the existing
$(DefineConstants) token and then the SDK symbols (e.g. NET10_0, WINDOWS or
other platform/runtime symbols you expect) to the DefineConstants attribute;
locate the DefineConstants attributes in the Flow.Launcher.Avalonia.csproj and
change them to include $(DefineConstants) followed by the additional constants
(keep DEBUG/TRACE/RELEASE/AVALONIA as appropriate).
- Around line 34-46: Summary: Several PackageReference entries in
Flow.Launcher.Avalonia.csproj (Avalonia, Avalonia.Desktop,
Avalonia.Themes.Fluent, Avalonia.Fonts.Inter, FluentAvaloniaUI,
CommunityToolkit.Mvvm, DynamicData) do not ship explicit net10.0 assets and rely
on NuGet compatibility fallbacks. Fix: in Flow.Launcher.Avalonia.csproj inspect
each PackageReference (e.g., Avalonia 11.2.3, FluentAvaloniaUI 2.2.0,
CommunityToolkit.Mvvm 8.4.0, DynamicData 9.0.4) and either upgrade to package
versions that explicitly target net10.0 or add a comment/verification step in
the csproj/PR to confirm net10 compatibility; if no net10 build exists, document
acceptance of NuGet fallback or replace with alternatives that support net10.0;
ensure Microsoft.Extensions.* preview packages are intentionally used
(DependencyInjection and Hosting) and update them to GA net10-compatible
versions where available.

In `@Flow.Launcher.Test/Flow.Launcher.Test.csproj`:
- Line 4: The repository SDK pinning and CI use .NET 9 while projects target
net10.0; update the SDK and CI to .NET 10 by changing the "version" value in
global.json's "sdk" object to a 10.x value (e.g., "10.0.*") and update the
GitHub Actions setup step(s) (actions/setup-dotnet invocation(s)) to use
dotnet-version: 10.0.x (including any matrix entries or multiple workflow files
like the two CI workflows) so the build toolchain matches the net10.0 target
across projects.

In `@Flow.Launcher/Flow.Launcher.csproj`:
- Around line 147-148: Update the two PackageReference entries that currently
point to the preview version "10.0.0-preview.1.25080.5" so they reference a
stable release (preferably "10.0.2" or at minimum "10.0.0"); specifically change
the PackageReference for Microsoft.Extensions.DependencyInjection and
Microsoft.Extensions.Hosting to the stable version string to avoid using preview
packages.

Comment on lines +20 to +28
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<OutputPath>..\Output\Debug\Avalonia\</OutputPath>
<DefineConstants>DEBUG;TRACE;AVALONIA</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<OutputPath>..\Output\Release\Avalonia\</OutputPath>
<DefineConstants>TRACE;RELEASE;AVALONIA</DefineConstants>
</PropertyGroup>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for conditional compilation that depends on SDK-defined constants
rg -nP '^\s*#if\s+(NET|NET10_0|NET10_0_WINDOWS|WINDOWS)\b' --type cs

Repository: Flow-Launcher/Flow.Launcher

Length of output: 53


🏁 Script executed:

#!/bin/bash
# Check if there are ANY conditional compilation directives in C# files
rg -nP '^\s*#if\b' --type cs | head -20

Repository: Flow-Launcher/Flow.Launcher

Length of output: 1628


🏁 Script executed:

#!/bin/bash
# Check the TargetFramework and other project properties in the .csproj file
rg -A 2 'TargetFramework|DefineConstants' Flow.Launcher.Avalonia/Flow.Launcher.Avalonia.csproj

Repository: Flow-Launcher/Flow.Launcher

Length of output: 623


Append SDK constants to preserve best practice consistency.

While the codebase doesn't currently depend on SDK-defined constants (like NET10_0 or WINDOWS) in conditional compilation, it's best practice to append to $(DefineConstants) rather than override it to safeguard against future uses and maintain consistency.

🛠️ Proposed fix
-    <DefineConstants>DEBUG;TRACE;AVALONIA</DefineConstants>
+    <DefineConstants>$(DefineConstants);DEBUG;TRACE;AVALONIA</DefineConstants>
...
-    <DefineConstants>TRACE;RELEASE;AVALONIA</DefineConstants>
+    <DefineConstants>$(DefineConstants);TRACE;RELEASE;AVALONIA</DefineConstants>
📝 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
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<OutputPath>..\Output\Debug\Avalonia\</OutputPath>
<DefineConstants>DEBUG;TRACE;AVALONIA</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<OutputPath>..\Output\Release\Avalonia\</OutputPath>
<DefineConstants>TRACE;RELEASE;AVALONIA</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<OutputPath>..\Output\Debug\Avalonia\</OutputPath>
<DefineConstants>$(DefineConstants);DEBUG;TRACE;AVALONIA</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<OutputPath>..\Output\Release\Avalonia\</OutputPath>
<DefineConstants>$(DefineConstants);TRACE;RELEASE;AVALONIA</DefineConstants>
</PropertyGroup>
🤖 Prompt for AI Agents
In `@Flow.Launcher.Avalonia/Flow.Launcher.Avalonia.csproj` around lines 20 - 28,
The PropertyGroup entries currently replace the DefineConstants value; update
both Debug and Release PropertyGroup blocks so they append SDK/product constants
instead of overriding by adding the existing $(DefineConstants) token and then
the SDK symbols (e.g. NET10_0, WINDOWS or other platform/runtime symbols you
expect) to the DefineConstants attribute; locate the DefineConstants attributes
in the Flow.Launcher.Avalonia.csproj and change them to include
$(DefineConstants) followed by the additional constants (keep
DEBUG/TRACE/RELEASE/AVALONIA as appropriate).

Comment on lines +34 to +46
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.2.3" />
<PackageReference Include="Avalonia.Desktop" Version="11.2.3" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.3" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.3" />
<PackageReference Include="FluentAvaloniaUI" Version="2.2.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="DynamicData" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0-preview.1.25080.5" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0-preview.1.25080.5" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Avalonia.Diagnostics" Version="11.2.3" Condition="'$(Configuration)' == 'Debug'" />
</ItemGroup>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -name "Flow.Launcher.Avalonia.csproj" -type f

Repository: Flow-Launcher/Flow.Launcher

Length of output: 125


🏁 Script executed:

cat -n ./Flow.Launcher.Avalonia/Flow.Launcher.Avalonia.csproj | head -50

Repository: Flow-Launcher/Flow.Launcher

Length of output: 3087


🌐 Web query:

Avalonia 11.2.3 net10.0 target framework support

💡 Result:

Avalonia 11.2.3 does not ship “net10.0” target frameworks (no lib/net10.0/* assets). For example, Avalonia and Avalonia.Desktop 11.2.3 target net6.0, net8.0, and netstandard2.0. [1][2]

That said, a net10.0 app can still reference Avalonia 11.2.3: NuGet marks these packages as compatible with net10.0 via framework compatibility rules (it will select the closest matching assets, e.g., net8.0/net6.0). [1][2]

Platform note: Avalonia.Android 11.2.3 targets net8.0-android34.0, and NuGet also computes compatibility for net10.0-android (but it’s still not a native net10.0-android* asset). [3]

Sources: [1] [2] [3]


🌐 Web query:

FluentAvaloniaUI 2.2.0 net10.0 target framework support

💡 Result:

FluentAvaloniaUI 2.2.0 does not ship a net10.0-targeted build. On NuGet, the package’s included TFMs are net6.0 and .NETStandard 2.0/2.1. (nuget.org)

If your app targets net10.0, NuGet will still allow the reference because it’s computed as compatible (“net10.0 was computed”) and it will fall back to using the package’s net6.0` (or .NET Standard) assets. (nuget.org)

Citations:


🌐 Web query:

CommunityToolkit.Mvvm 8.4.0 net10.0 target framework support

💡 Result:

CommunityToolkit.Mvvm 8.4.0 does not ship a net10.0-specific build. Its included TFMs are:

  • netstandard2.0
  • netstandard2.1
  • net8.0
  • net8.0-windows10.0.17763 (nuget.org)

A project targeting net10.0 can still reference it, because NuGet marks the package as compatible with “.NET 8.0 or higher” and lists net10.0 as a computed compatible TFM (meaning it will typically use the net8.0 assets). (nuget.org)

Citations:


🌐 Web query:

DynamicData 9.0.4 net10.0 target framework support

💡 Result:

DynamicData 9.0.4 does not ship a net10.0-targeted assembly. Its included TFMs are:

A project targeting net10.0 can still reference DynamicData 9.0.4 because net10.0 is compatible with libraries targeting net6.0 (NuGet will select the net6.0 asset). (nuget.org)

Citations:


🌐 Web query:

Microsoft.Extensions.DependencyInjection 10.0.0-preview.1.25080.5 net10.0 support

💡 Result:

Yes. Microsoft.Extensions.DependencyInjection 10.0.0-preview.1.25080.5 includes an explicit net10.0 target (alongside net8.0, net9.0, netstandard2.0, netstandard2.1, and net462). [1]


🌐 Web query:

Microsoft.Extensions.Hosting 10.0.0-preview.1.25080.5 net10.0 support

💡 Result:

Yes—Microsoft.Extensions.Hosting 10.0.0-preview.1.25080.5 can be used from a net10.0 project.

On NuGet, the package includes assets for net8.0 and netstandard2.0, and NuGet marks it as compatible with net10.0 (computed compatibility), meaning a net10.0 app will consume the net8.0 (or netstandard2.0) build rather than a net10.0-specific build. [1]

What this means: you can reference it in net10.0, but you shouldn’t expect net10.0-only APIs/behavior unless a later package version ships net10.0-targeted assets. [1]


Most package versions lack explicit net10.0 support; they rely on NuGet framework compatibility rules.

Avalonia 11.2.3, FluentAvaloniaUI 2.2.0, CommunityToolkit.Mvvm 8.4.0, and DynamicData 9.0.4 do not ship net10.0-targeted builds. Instead, they target net8.0, net6.0, or netstandard2.0, and NuGet marks them as compatible with net10.0 by falling back to the closest matching assets (typically net8.0). While this arrangement works, it means these packages are not explicitly tested or optimized for .NET 10. The Microsoft.Extensions packages (10.0.0-preview.1.25080.5) are an exception: Microsoft.Extensions.DependencyInjection includes explicit net10.0 assets, though Microsoft.Extensions.Hosting relies on computed compatibility to net8.0 assets.

🤖 Prompt for AI Agents
In `@Flow.Launcher.Avalonia/Flow.Launcher.Avalonia.csproj` around lines 34 - 46,
Summary: Several PackageReference entries in Flow.Launcher.Avalonia.csproj
(Avalonia, Avalonia.Desktop, Avalonia.Themes.Fluent, Avalonia.Fonts.Inter,
FluentAvaloniaUI, CommunityToolkit.Mvvm, DynamicData) do not ship explicit
net10.0 assets and rely on NuGet compatibility fallbacks. Fix: in
Flow.Launcher.Avalonia.csproj inspect each PackageReference (e.g., Avalonia
11.2.3, FluentAvaloniaUI 2.2.0, CommunityToolkit.Mvvm 8.4.0, DynamicData 9.0.4)
and either upgrade to package versions that explicitly target net10.0 or add a
comment/verification step in the csproj/PR to confirm net10 compatibility; if no
net10 build exists, document acceptance of NuGet fallback or replace with
alternatives that support net10.0; ensure Microsoft.Extensions.* preview
packages are intentionally used (DependencyInjection and Hosting) and update
them to GA net10-compatible versions where available.


<PropertyGroup>
<TargetFramework>net9.0-windows10.0.19041.0</TargetFramework>
<TargetFramework>net10.0-windows10.0.19041.0</TargetFramework>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for SDK pinning configuration
echo "=== Checking global.json for SDK pinning ==="
if [ -f global.json ]; then
  cat global.json
else
  echo "No global.json found at root"
fi

echo -e "\n=== Checking Directory.Build.props for framework configuration ==="
if [ -f Directory.Build.props ]; then
  cat Directory.Build.props
else
  echo "No Directory.Build.props found at root"
fi

echo -e "\n=== Searching all .csproj files for TargetFramework ==="
find . -name "*.csproj" -type f | head -20 | while read file; do
  echo "File: $file"
  rg -n 'TargetFramework' "$file" || true
done

echo -e "\n=== Checking for net10 references across codebase ==="
rg -n 'net10\.0' --type=csproj

echo -e "\n=== Checking CI configuration (common locations) ==="
for ci_file in .github/workflows/*.yml .github/workflows/*.yaml azure-pipelines.yml .gitlab-ci.yml .circleci/config.yml; do
  if [ -f "$ci_file" ]; then
    echo "Found CI file: $ci_file"
    rg -n 'dotnet-version|DOTNET_VERSION|sdk-version' "$ci_file" || echo "No SDK version spec in $ci_file"
  fi
done

echo -e "\n=== Checking Flow.Launcher.Test.csproj in detail ==="
cat -n Flow.Launcher.Test/Flow.Launcher.Test.csproj 2>/dev/null || echo "File not found"

Repository: Flow-Launcher/Flow.Launcher

Length of output: 8955


Update global.json and CI workflows to .NET 10 before merge; entire codebase is misaligned.

The test project is already using net10.0-windows10.0.19041.0, but the repository's SDK pinning in global.json is still locked to 9.0.* and CI workflows specify dotnet-version: 9.0.x. This mismatch affects the entire codebase—multiple projects have been upgraded to net10.0 (Flow.Launcher.Core, Flow.Launcher.Infrastructure, Flow.Launcher, plugins)—but the build toolchain hasn't been updated. CI will fail to build any net10.0 project with .NET 9. Update global.json to pin .NET 10 and update .github/workflows/dotnet.yml and .github/workflows/default_plugins.yml to use dotnet-version: 10.0.x. The test packages (Moq 4.20.72, nunit 4.4.0, NUnit3TestAdapter 5.1.0, Microsoft.NET.Test.Sdk 17.14.1) are all compatible with .NET 10.

🤖 Prompt for AI Agents
In `@Flow.Launcher.Test/Flow.Launcher.Test.csproj` at line 4, The repository SDK
pinning and CI use .NET 9 while projects target net10.0; update the SDK and CI
to .NET 10 by changing the "version" value in global.json's "sdk" object to a
10.x value (e.g., "10.0.*") and update the GitHub Actions setup step(s)
(actions/setup-dotnet invocation(s)) to use dotnet-version: 10.0.x (including
any matrix entries or multiple workflow files like the two CI workflows) so the
build toolchain matches the net10.0 target across projects.

Comment on lines +147 to +148
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0-preview.1.25080.5" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0-preview.1.25080.5" />
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Microsoft.Extensions.DependencyInjection 10.0 stable release NuGet

💡 Result:

  • Microsoft.Extensions.DependencyInjection 10.0 stable (GA) NuGet release: 10.0.0, published Nov 11, 2025. [1]
  • Latest 10.0.x stable currently on NuGet: 10.0.2, published Jan 13, 2026. [2]

Install (NuGet Package Manager):

Install-Package Microsoft.Extensions.DependencyInjection -Version 10.0.0

(or omit -Version to get the latest stable 10.0.x). [2]

Related package: Microsoft.Extensions.DependencyInjection.Abstractions is also at 10.0.2 (Jan 13, 2026). [3]


🌐 Web query:

Microsoft.Extensions.Hosting 10.0 stable release NuGet

💡 Result:

The stable 10.0 release of Microsoft.Extensions.Hosting is published on NuGet as:

  • 10.0.0 (released Nov 11, 2025) [1]
  • Latest stable 10.0.x: 10.0.2 (released Jan 13, 2026) [2]

Install (latest stable 10.0.x):

dotnet add package Microsoft.Extensions.Hosting --version 10.0.2

Or pin the first stable 10.0:

dotnet add package Microsoft.Extensions.Hosting --version 10.0.0

Update to stable versions of Microsoft.Extensions packages.

The Microsoft.Extensions.DependencyInjection and Microsoft.Extensions.Hosting packages are pinned to preview version 10.0.0-preview.1.25080.5. Stable versions 10.0.0 were released Nov 11, 2025, and the latest stable is 10.0.2 (Jan 13, 2026). Upgrade to the stable release to avoid preview-phase breaking changes and bugs.

🤖 Prompt for AI Agents
In `@Flow.Launcher/Flow.Launcher.csproj` around lines 147 - 148, Update the two
PackageReference entries that currently point to the preview version
"10.0.0-preview.1.25080.5" so they reference a stable release (preferably
"10.0.2" or at minimum "10.0.0"); specifically change the PackageReference for
Microsoft.Extensions.DependencyInjection and Microsoft.Extensions.Hosting to the
stable version string to avoid using preview packages.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

30 min review bug Something isn't working enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants