Skip to content

Avalonia migration#4214

Open
taooceros wants to merge 62 commits intodevfrom
avalonia_migration
Open

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

Conversation

@taooceros
Copy link
Copy Markdown
Member

@taooceros taooceros commented Jan 15, 2026

This PR introduces the experimental Avalonia UI host alongside the existing WPF host so the migration can continue incrementally while preserving side-by-side comparison.

The current migration tracker is AVALONIA_MIGRATION_CHECKLIST.md.

Summary

  • Adds Flow.Launcher.Avalonia, a .NET 9 Avalonia host with its own output directory at Output/Debug/Avalonia.
  • Reuses the existing shared projects: Flow.Launcher.Plugin, Flow.Launcher.Infrastructure, and Flow.Launcher.Core.
  • Keeps the WPF host intact while Avalonia parity is built out.
  • Uses CommunityToolkit.Mvvm patterns in the Avalonia host.
  • Current overall progress is approximately 70-75%.

What Works

  • Main window query flow with plugin-backed results.
  • Global hotkey show/hide support.
  • Result display, selection, activation, context menu, icons, and match highlighting.
  • Progressive/async plugin querying and image loading.
  • Settings shell and most migrated settings pages.
  • Plugin management and plugin store flows.
  • System tray, auto-start, portable mode wiring, message dialogs, report window, welcome/release-notes windows, and plugin update progress dialog.

Migration Checklist

Windows & Dialogs — 16/19 complete (84%)

  • Main window, settings window, result list, preview panel, welcome/report/release-notes dialogs.
  • Browser/file-manager selection windows, plugin update window, message/notification flows, progress dialog.
  • Remaining parity: custom hotkey/shortcut editor windows. Action keyword editing is handled by a dialog-based replacement.

Settings Pages

  • General settings — ~95% complete.
  • Proxy settings — 100% complete.
  • Plugin settings — ~90% complete.
  • Plugin store — ~95% complete.
  • About settings — ~90% complete.
  • Theme settings — ~75%; remaining gaps include window size slider, system backdrop parity, icon theme, and double-click icon action.
  • Hotkey settings — ~67%; add/edit custom query hotkey and shortcut dialogs still pending.

Core UX / ViewModels

  • Basic query, result display, keyboard navigation, activation, context menu, text highlighting, and history cycling.
  • Hide-on-focus-loss, topmost mode, window dragging, system tray, global hotkey, auto-start, portable mode.
  • Remaining MainViewModel parity: clipboard paste handling, auto-complete suggestions, text selection handling, preview toggle, dialog jump behavior, game mode detection, window position memory, and IME mode control.
  • Single-instance enforcement and multi-monitor position parity still need work.

Helpers, Converters, and Controls

  • Hotkey mapping/registration, image loading, font loading, text formatting, auto-start helper.
  • Card, ExCard, CardGroup, HotkeyControl, HotkeyRecorderDialog, and HotkeyDisplay controls.
  • Basic visibility, localization, and common converters.
  • Remaining helper parity: single instance, exception formatting, singleton window opener, wallpaper path retrieval, media preview helper, proxy-aware web requests, and syntax sugar helpers.
  • Remaining converter parity: query suggestions, text transforms, sizing/badge/icon/date/border/IME converters, and related WPF-specific visual helpers.

Theming and Polish

  • Light/dark/system mode, theme selection UI, live theme preview, font/size controls, animation speed setting, progress indicators.
  • Custom theme loading, full backdrop effects, icon themes, and most window/result/menu/settings transition animations.

Build & Run

dotnet build Flow.Launcher.Avalonia/Flow.Launcher.Avalonia.csproj
./Output/Debug/Avalonia/Flow.Launcher.Avalonia.exe

Notes for Reviewers

  • Treat AVALONIA_MIGRATION_CHECKLIST.md as the detailed source of truth for parity status and remaining gaps.
  • This PR is still a migration branch, not a claim that Avalonia has full WPF parity.

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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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.

@jjw24 jjw24 marked this pull request as draft February 22, 2026 02:38
@jjw24 jjw24 modified the milestones: 3.0.0, Future Feb 22, 2026
@github-actions
Copy link
Copy Markdown

@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 199
❌ 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.

taooceros and others added 3 commits March 26, 2026 14:12
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Split repository guidance by domain so contributors can find module-specific rules without duplicating repo-wide conventions.
@jjw24
Copy link
Copy Markdown
Member

jjw24 commented Apr 11, 2026

@taooceros could you list us what's left to do in this PR please.

@taooceros
Copy link
Copy Markdown
Member Author

I will get a summary soon.

taooceros and others added 12 commits April 19, 2026 15:04
Bring the Avalonia host closer to WPF parity by wiring the missing settings dialogs, selectors, and public API flows. Also fix the async JSON save path and settings DI so direct-launch validation can get past the earlier runtime failures.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Track the requested local agent metadata in the repo and add a minimal OpenCode project config for editor/schema support. Keep the old .gemini workspace out of the repository state.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Move the new WPF-inspired palette into shared Avalonia theme resources so the main shell can pull consistent brushes and borders from one place. This keeps later styling polish tied to a single resource layer instead of scattered hex values.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Apply the shared theme brushes to the live result list and the theme settings preview so the settings sample matches the actual search window. This makes the new palette visible in the places users judge the UI most quickly.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Replace the rigid footer button rows on the About page with flexible wrapping so longer labels do not clip or disappear. This keeps the action layout stable across different widths and localized text lengths.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Mark the completed dialogs, settings work, and runtime wiring so the migration plan matches the current Avalonia host state. This keeps the remaining work list focused on the gaps that still matter instead of already-finished items.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@taooceros taooceros marked this pull request as ready for review May 2, 2026 19:55
Copy link
Copy Markdown
Contributor

@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.

15 issues found across 291 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. cubic prioritises the most important files to review.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


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

<violation number="1" location="Flow.Launcher.Avalonia/Views/Controls/HotkeyControl.axaml.cs:66">
P2: Replace the hardcoded "None" text with a localized string so the hotkey empty state respects current UI language.</violation>

<violation number="2" location="Flow.Launcher.Avalonia/Views/Controls/HotkeyControl.axaml.cs:94">
P1: When saving a new toggle hotkey, re-register the new value; current logic only re-registers when the value is unchanged.</violation>
</file>

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

<violation number="1" location="Flow.Launcher.Avalonia/Views/SettingPages/SettingsWindow.axaml:9">
P3: Localize the window title instead of hardcoding English text.</violation>
</file>

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

<violation number="1" location="Flow.Launcher.Avalonia/Views/Dialogs/ProgressBoxWindow.axaml:17">
P3: Avoid hardcoded `FontSize` here and rely on default/shared text styles for consistent theming and accessibility.

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

<file name=".gitignore">

<violation number="1" location=".gitignore:308">
P2: The `.history` and `codemap.md` ignore entries were accidentally concatenated into one pattern, so `.history` is no longer properly ignored and the codemap rule is mis-scoped.</violation>
</file>

<file name=".github/workflows/dotnet.yml">

<violation number="1" location=".github/workflows/dotnet.yml:95">
P2: Use the same `actions/upload-artifact` major version as the rest of the workflow (`@v7`) to avoid inconsistent behavior across artifact upload steps.</violation>
</file>

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

<violation number="1" location="Flow.Launcher.Avalonia/Views/SettingPages/CustomQueryHotkeyWindow.axaml:25">
P3: Localize this hardcoded help text instead of embedding English directly in the view.</violation>
</file>

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

<violation number="1" location="Flow.Launcher.Avalonia/Storage/History.cs:26">
P2: Check for an existing history entry before evicting; otherwise duplicate executions at capacity delete an unrelated item.</violation>
</file>

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

<violation number="1" location="Flow.Launcher.Avalonia/Views/SettingPages/CustomShortcutWindow.axaml:24">
P2: Localize the new description text instead of hardcoding English so it participates in the app’s i18n flow.</violation>
</file>

<file name="Flow.Launcher.Avalonia/Views/Dialogs/NotificationWindow.axaml.cs">

<violation number="1" location="Flow.Launcher.Avalonia/Views/Dialogs/NotificationWindow.axaml.cs:104">
P2: Notification stacking can overlap when a non-last toast closes, because positioning uses only a global active count instead of actual occupied slots.</violation>
</file>

<file name="Flow.Launcher.Avalonia/Views/Dialogs/ProgressBoxWindow.axaml.cs">

<violation number="1" location="Flow.Launcher.Avalonia/Views/Dialogs/ProgressBoxWindow.axaml.cs:90">
P2: Hidden progress windows are not cleaned up because close is gated by `IsVisible`; if the user clicks Hide, `finally` skips `Close()` and the window instance is left alive.</violation>
</file>

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

<violation number="1" location="Flow.Launcher.Core/Flow.Launcher.Core.csproj:4">
P1: This TFM upgrade to `net10.0-windows` is incompatible with the repository’s `global.json` SDK pin (`9.0.*`), which can break local/CI builds with NETSDK1045 unless the SDK pin is updated in the same PR.</violation>
</file>

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

<violation number="1" location="Flow.Launcher.Avalonia/Resource/LocalizeExtension.cs:47">
P2: Return a dynamic resource binding instead of a literal translation string so localized text updates after runtime language changes.</violation>
</file>

<file name="Flow.Launcher.Avalonia/Views/Dialogs/ReportWindow.axaml.cs">

<violation number="1" location="Flow.Launcher.Avalonia/Views/Dialogs/ReportWindow.axaml.cs:49">
P2: Validate `IssueUrl` before `Process.Start`; malformed URLs can throw and make the report dialog fail when users click Open Issue.</violation>

<violation number="2" location="Flow.Launcher.Avalonia/Views/Dialogs/ReportWindow.axaml.cs:70">
P2: Guard `pluginException.Metadata.Website` before calling `StartsWith`; a null website can throw and break the error-report flow.</violation>
</file>

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

{
Hotkey = dialog.ResultValue;

if (shouldUnregisterToggle && string.Equals(dialog.ResultValue, originalHotkey, System.StringComparison.Ordinal))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: When saving a new toggle hotkey, re-register the new value; current logic only re-registers when the value is unchanged.

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 94:

<comment>When saving a new toggle hotkey, re-register the new value; current logic only re-registers when the value is unchanged.</comment>

<file context>
@@ -0,0 +1,112 @@
+            {
+                Hotkey = dialog.ResultValue;
+
+                if (shouldUnregisterToggle && string.Equals(dialog.ResultValue, originalHotkey, System.StringComparison.Ordinal))
+                {
+                    HotKeyMapper.SetToggleHotkey(originalHotkey);
</file context>


<PropertyGroup>
<TargetFramework>net9.0-windows</TargetFramework>
<TargetFramework>net10.0-windows</TargetFramework>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: This TFM upgrade to net10.0-windows is incompatible with the repository’s global.json SDK pin (9.0.*), which can break local/CI builds with NETSDK1045 unless the SDK pin is updated in the same PR.

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>This TFM upgrade to `net10.0-windows` is incompatible with the repository’s `global.json` SDK pin (`9.0.*`), which can break local/CI builds with NETSDK1045 unless the SDK pin is updated in the same PR.</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>

KeysToDisplay.Clear();
if (string.IsNullOrEmpty(Hotkey))
{
KeysToDisplay.Add("None");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Replace the hardcoded "None" text with a localized string so the hotkey empty state respects current UI language.

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 66:

<comment>Replace the hardcoded "None" text with a localized string so the hotkey empty state respects current UI language.</comment>

<file context>
@@ -0,0 +1,112 @@
+            KeysToDisplay.Clear();
+            if (string.IsNullOrEmpty(Hotkey))
+            {
+                KeysToDisplay.Add("None");
+                return;
+            }
</file context>

Comment thread .gitignore
.vscode
.history No newline at end of file
.history**/codemap.md
.slim/
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: The .history and codemap.md ignore entries were accidentally concatenated into one pattern, so .history is no longer properly ignored and the codemap rule is mis-scoped.

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

<comment>The `.history` and `codemap.md` ignore entries were accidentally concatenated into one pattern, so `.history` is no longer properly ignored and the codemap rule is mis-scoped.</comment>

<file context>
@@ -304,4 +304,34 @@ Output-Performance.txt
-.history
\ No newline at end of file
+.history**/codemap.md
+.slim/
+
+# Local planning artifacts
</file context>

Output\Packages\RELEASES
compression-level: 0
- name: Upload Avalonia Build
uses: actions/upload-artifact@v5
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Use the same actions/upload-artifact major version as the rest of the workflow (@v7) to avoid inconsistent behavior across artifact upload steps.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/dotnet.yml, line 95:

<comment>Use the same `actions/upload-artifact` major version as the rest of the workflow (`@v7`) to avoid inconsistent behavior across artifact upload steps.</comment>

<file context>
@@ -89,3 +91,10 @@ jobs:
             Output\Packages\RELEASES
           compression-level: 0
+      - name: Upload Avalonia Build
+        uses: actions/upload-artifact@v5
+        with:
+          name: Flow Launcher Avalonia
</file context>
Suggested change
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v7


private void OnOpenIssueClick(object? sender, RoutedEventArgs e)
{
Process.Start(new ProcessStartInfo(IssueUrl) { UseShellExecute = true });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Validate IssueUrl before Process.Start; malformed URLs can throw and make the report dialog fail when users click Open Issue.

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

<comment>Validate `IssueUrl` before `Process.Start`; malformed URLs can throw and make the report dialog fail when users click Open Issue.</comment>

<file context>
@@ -0,0 +1,119 @@
+
+    private void OnOpenIssueClick(object? sender, RoutedEventArgs e)
+    {
+        Process.Start(new ProcessStartInfo(IssueUrl) { UseShellExecute = true });
+    }
+
</file context>

}

var website = pluginException.Metadata.Website;
if (!website.StartsWith("https://github.com", StringComparison.OrdinalIgnoreCase))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Guard pluginException.Metadata.Website before calling StartsWith; a null website can throw and break the error-report flow.

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

<comment>Guard `pluginException.Metadata.Website` before calling `StartsWith`; a null website can throw and break the error-report flow.</comment>

<file context>
@@ -0,0 +1,119 @@
+        }
+
+        var website = pluginException.Metadata.Website;
+        if (!website.StartsWith("https://github.com", StringComparison.OrdinalIgnoreCase))
+        {
+            return website;
</file context>

xmlns:i18n="using:Flow.Launcher.Avalonia.Resource"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Flow.Launcher.Avalonia.Views.SettingPages.SettingsWindow"
Title="Flow Launcher Settings"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: Localize the window title instead of hardcoding English text.

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/SettingsWindow.axaml, line 9:

<comment>Localize the window title instead of hardcoding English text.</comment>

<file context>
@@ -0,0 +1,31 @@
+        xmlns:i18n="using:Flow.Launcher.Avalonia.Resource"
+        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+        x:Class="Flow.Launcher.Avalonia.Views.SettingPages.SettingsWindow"
+        Title="Flow Launcher Settings"
+        Width="900" Height="650"
+        WindowStartupLocation="CenterScreen">
</file context>

WindowStartupLocation="CenterScreen">

<Grid RowDefinitions="Auto,Auto,*,Auto" Margin="20">
<TextBlock FontSize="20"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: Avoid hardcoded FontSize here and rely on default/shared text styles for consistent theming and accessibility.

(Based on your team's feedback about relying on default UI styles and avoiding hardcoded 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/Dialogs/ProgressBoxWindow.axaml, line 17:

<comment>Avoid hardcoded `FontSize` here and rely on default/shared text styles for consistent theming and accessibility.

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

<file context>
@@ -0,0 +1,41 @@
+        WindowStartupLocation="CenterScreen">
+
+    <Grid RowDefinitions="Auto,Auto,*,Auto" Margin="20">
+        <TextBlock FontSize="20"
+                   FontWeight="SemiBold"
+                   Text="{Binding TitleText}" />
</file context>

FontSize="20"
FontWeight="SemiBold" />

<TextBlock Text="Choose a hotkey and the query text to insert when it is pressed."
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: Localize this hardcoded help text instead of embedding English directly in the view.

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/CustomQueryHotkeyWindow.axaml, line 25:

<comment>Localize this hardcoded help text instead of embedding English directly in the view.</comment>

<file context>
@@ -0,0 +1,67 @@
+                       FontSize="20"
+                       FontWeight="SemiBold" />
+
+            <TextBlock Text="Choose a hotkey and the query text to insert when it is pressed."
+                       TextWrapping="Wrap" />
+
</file context>

Copy link
Copy Markdown
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: 19

Note

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

♻️ Duplicate comments (2)
Flow.Launcher.Avalonia/App.axaml.cs (1)

142-150: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Attach storage to the fallback Settings instance too.

The failure path still creates _settings without SetStorage, so later saves run against an unconfigured settings object.

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
Verify each finding against the current code and only fix it if needed.

In `@Flow.Launcher.Avalonia/App.axaml.cs` around lines 142 - 150, The fallback
Settings instance created in the catch block is missing its storage attachment,
so call SetStorage on the newly created _settings (the same storage used for the
normal settings path) before assigning/using it; update the catch to create the
Settings object and then invoke _settings.SetStorage(...) with the existing
settings storage instance (the same storage reference used elsewhere when
loading/saving settings), ensuring the fallback has storage configured.
Flow.Launcher.Avalonia/MainWindow.axaml (1)

68-75: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make the search icon overlay non-hit-testable.

This overlay sits on top of the query box, so clicks on the icon area can stop focusing the TextBox.

Suggested fix
-                <Canvas Name="SearchIconCanvas" 
-                        Classes="searchIconPosition"
-                        HorizontalAlignment="Right">
+                <Canvas Name="SearchIconCanvas"
+                        Classes="searchIconPosition"
+                        HorizontalAlignment="Right"
+                        IsHitTestVisible="False">
In Avalonia UI, does an overlaid Canvas receive pointer events by default, and does setting `IsHitTestVisible="False"` allow clicks to pass through to the TextBox underneath?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Flow.Launcher.Avalonia/MainWindow.axaml` around lines 68 - 75, The overlaid
SearchIconCanvas is intercepting pointer events and preventing the underlying
TextBox from receiving focus; make the overlay non-hit-testable by setting
IsHitTestVisible="False" (on SearchIconCanvas or on the inner SearchIcon Path)
so pointer events pass through to the TextBox beneath, ensuring clicks still
focus the input while preserving the visual overlay.
🟡 Minor comments (22)
Flow.Launcher.Avalonia/Views/Dialogs/NotificationWindow.axaml.cs-87-94 (1)

87-94: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle icon-load failures explicitly to avoid silent task faults.

LoadIconAsync is fire-and-forget and currently has no local exception handling. A bad/corrupt image path can fault the task without structured logging.

Suggested fix
     private async System.Threading.Tasks.Task LoadIconAsync(string iconPath)
     {
-        var resolvedPath = File.Exists(iconPath)
-            ? iconPath
-            : Path.Combine(Constant.ProgramDirectory, "Images", "app.png");
-
-        NotificationIcon = await ImageLoader.LoadAsync(resolvedPath);
+        try
+        {
+            var resolvedPath = File.Exists(iconPath)
+                ? iconPath
+                : Path.Combine(Constant.ProgramDirectory, "Images", "app.png");
+
+            NotificationIcon = await ImageLoader.LoadAsync(resolvedPath);
+        }
+        catch (Exception ex)
+        {
+            Log.Exception(nameof(NotificationWindow), "Failed to load notification icon", ex);
+            NotificationIcon = null;
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Flow.Launcher.Avalonia/Views/Dialogs/NotificationWindow.axaml.cs` around
lines 87 - 94, LoadIconAsync currently awaits ImageLoader.LoadAsync but has no
exception handling, causing silent task faults; wrap the
ImageLoader.LoadAsync(resolvedPath) call in a try/catch inside LoadIconAsync
(catch Exception) and on failure set a safe fallback for NotificationIcon and
log the exception (include resolvedPath and the exception) using the existing
logging mechanism so failures are visible and the UI remains stable.
AGENTS.md-5-16 (1)

5-16: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Root AGENTS table needs MD058 spacing fix.

Add blank lines around the WHERE TO LOOK table block.

Suggested patch
 ## WHERE TO LOOK
+
 | Task | Location | Notes |
 |------|----------|-------|
 | WPF app behavior | `Flow.Launcher/` | Main host, windows, settings shell, WPF-only UI rules |
 | Avalonia app behavior | `Flow.Launcher.Avalonia/` | Avalonia host, WPF compatibility shim, migrated settings pages |
 | Plugin lifecycle | `Flow.Launcher.Core/` | `PluginManager`, plugin loading, updates, manifest integration |
 | Shared infra | `Flow.Launcher.Infrastructure/` | settings, storage, hotkeys, logging, fuzzy matching |
 | Plugin SDK contract | `Flow.Launcher.Plugin/` | interfaces/models consumed by built-in and external plugins |
 | Built-in plugins | `Plugins/` | 12 plugin projects, shared `Main.cs` + `plugin.json` structure |
 | Test project | `Flow.Launcher.Test/` | NUnit 4 tests; some Explorer tests need Windows Search |
 | CI / release process | `.github/` | workflow automation, release PRs, plugin publishing, issue templates |
+
 ## CHILD AGENTS
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AGENTS.md` around lines 5 - 16, The WHERE TO LOOK table in AGENTS.md violates
MD058 (missing blank lines around a block table); edit AGENTS.md and add a blank
line immediately before the "## WHERE TO LOOK" heading block and another blank
line immediately after the table (after the final | CI / release process |
`.github/` ... | row) so the table is separated from surrounding text, ensuring
proper Markdown spacing for the "WHERE TO LOOK" table block.
Flow.Launcher.Infrastructure/AGENTS.md-5-14 (1)

5-14: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add blank lines around the table (MD058).

markdownlint expects a blank line before and after this table block.

Suggested patch
 ## WHERE TO LOOK
+
 | Task | Location | Notes |
 |------|----------|-------|
 | User settings | `UserSettings/` | `Settings`, `DataLocation`, app/user data roots |
 | Storage | `Storage/` | JSON/binary persistence, backup/atomic write behavior |
 | Search matching | `StringMatcher.cs`, related models | fuzzy/acronym/pinyin behavior |
 | Logging | `Logger/` | NLog wrapper and diagnostics |
 | Hotkeys | `Hotkey/` | low-level keyboard integration |
 | Windows helpers | helper/system wrappers | Win32 and shell integration |
+
 ## LOCAL RULES
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Flow.Launcher.Infrastructure/AGENTS.md` around lines 5 - 14, The Markdown
table under the "## WHERE TO LOOK" heading needs blank lines before and after it
to satisfy markdownlint rule MD058; edit the AGENTS.md content so there is an
empty line between the "## WHERE TO LOOK" heading and the table start, and
another empty line after the table end (i.e., ensure a blank line before the
first | of the table and one after the final | row) to resolve the lint warning.
.github/AGENTS.md-5-15 (1)

5-15: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Lint fix needed: blank lines around table (MD058).

Please add a blank line before and after this table.

Suggested patch
 ## WHERE TO LOOK
+
 | Task | Location | Notes |
 |------|----------|-------|
 | CI build and artifacts | `.github/workflows/dotnet.yml` | build/test pipeline, WSearch service, artifact publishing |
 | Release deploy | `.github/workflows/release_deploy.yml` | website + Chocolatey dispatches, secret-driven |
 | Release PR body automation | `.github/workflows/release_pr.yml`, `.github/update_release_pr.py` | expects one open release PR and milestone grouping |
 | Built-in plugin publishing | `.github/workflows/default_plugins.yml` | publishes plugin repos, updates plugin metadata |
 | Issue/PR automation | `.github/workflows/pr_*.yml`, `stale.yml`, `spelling.yml` | author assignment, milestone, stale, spelling |
 | Dependency policy | `.github/dependabot.yml` | daily cadence, PR caps, ignored packages |
 | Intake rules | `.github/ISSUE_TEMPLATE/` | bug report required fields, feature request, code review template |
+
 ## LOCAL RULES
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/AGENTS.md around lines 5 - 15, Add a blank line before and after the
Markdown table under the "WHERE TO LOOK" heading so the table is separated from
surrounding text (fixes MD058); locate the "WHERE TO LOOK" header and the
following table block (the lines starting with "## WHERE TO LOOK" and the table
rows like "| Task | Location | Notes |") and insert one empty line immediately
above the "## WHERE TO LOOK" table block if not present and one empty line
immediately after the table end.
Plugins/AGENTS.md-11-18 (1)

11-18: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Resolve markdownlint MD058 for the WHERE TO LOOK table.

Insert blank lines around this table block.

Suggested patch
 ## WHERE TO LOOK
+
 | Task | Location | Notes |
 |------|----------|-------|
 | Entry/query logic | each plugin `Main.cs` | sync vs async behavior lives here |
 | Manifest | each plugin `plugin.json` | metadata, action keyword(s), icon, execute file |
 | Settings model | `Settings.cs` or equivalent | usually loaded through API storage helpers |
 | Plugin UI/settings | plugin views/viewmodels if present | some have WPF + Avalonia settings paths |
+
 ## LOCAL RULES
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Plugins/AGENTS.md` around lines 11 - 18, The MD058 lint violation is caused
by the table not being separated by blank lines; update the Plugins/AGENTS.md by
adding a blank line after the "## WHERE TO LOOK" heading and another blank line
after the table block (the lines starting with "| Task | Location | Notes |"
through the table row with "Plugin UI/settings | plugin views/viewmodels if
present | ...") so the table is isolated with an empty line above and below to
satisfy markdownlint.
Flow.Launcher.Core/AGENTS.md-5-14 (1)

5-14: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix markdownlint MD058 around the table.

Add blank lines before/after the WHERE TO LOOK table to satisfy markdownlint.

Suggested patch
 ## WHERE TO LOOK
+
 | Task | Location | Notes |
 |------|----------|-------|
 | Plugin lifecycle | `Plugin/PluginManager.cs` | discovery, load, init, query fanout |
 | Plugin manifest parsing | `Plugin/PluginConfig.cs` | scans `plugin.json`, duplicate/version filtering |
 | Loader implementation | `Plugin/PluginsLoader.cs` | .NET + external plugin loading |
 | Community plugins | `ExternalPlugins/` | manifest/store integration |
 | Updates | updater-related files | Squirrel + release logic |
 | Resources/i18n | `Resource/`, related services | host/plugin resource merging |
+
 ## LOCAL RULES
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Flow.Launcher.Core/AGENTS.md` around lines 5 - 14, Add a blank line
immediately before the "## WHERE TO LOOK" heading/table and another blank line
immediately after the table to satisfy markdownlint MD058; edit the AGENTS.md
section containing the table that lists Plugin/PluginManager.cs,
Plugin/PluginConfig.cs, Plugin/PluginsLoader.cs, ExternalPlugins/,
updater-related files, and Resource/ so the table is separated by empty lines
above and below.
Flow.Launcher.Plugin/AGENTS.md-5-12 (1)

5-12: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Table spacing violates MD058.

Please add blank lines before and after this table.

Suggested patch
 ## WHERE TO LOOK
+
 | Task | Location | Notes |
 |------|----------|-------|
 | Core plugin interfaces | `Interfaces/` | `IPlugin`, `IAsyncPlugin`, capability interfaces |
 | Public models | root models, `SharedModels/` | `Result`, `Query`, metadata, settings-facing types |
 | Shared commands/helpers | `SharedCommands/` | reusable SDK-side pieces |
 | Docs | `README.md` | package/plugin authoring baseline |
+
 ## LOCAL RULES
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Flow.Launcher.Plugin/AGENTS.md` around lines 5 - 12, The Markdown table under
the "## WHERE TO LOOK" heading violates MD058 by lacking blank lines around the
table; fix it by inserting a single blank line immediately after the "## WHERE
TO LOOK" heading and another blank line immediately after the table block (the
pipe-delimited rows shown in the diff) so there is one empty line before and
after the table.
Flow.Launcher/AGENTS.md-5-13 (1)

5-13: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Surround the “WHERE TO LOOK” table with blank lines.

Line 6 currently triggers markdownlint MD058. Add a blank line before and after the table block.

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

In `@Flow.Launcher/AGENTS.md` around lines 5 - 13, The "WHERE TO LOOK" markdown
table in AGENTS.md triggers MD058 because it is not surrounded by blank lines;
update the file to insert a blank line immediately before the "## WHERE TO LOOK"
header/table block and another blank line immediately after the table so the
table is separated from surrounding content and satisfies markdownlint; target
the table block shown (the header "## WHERE TO LOOK" and its pipe-delimited
rows) when making the change.
Flow.Launcher.Avalonia/AGENTS.md-5-13 (1)

5-13: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add blank lines around the table block.

Line 6 starts a table immediately after a heading, which triggers markdownlint (MD058). Add one blank line before and after this table for clean rendering/lint pass.

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

In `@Flow.Launcher.Avalonia/AGENTS.md` around lines 5 - 13, Add a blank line
before and a blank line after the markdown table that starts with the heading
"## WHERE TO LOOK" and the pipe-delimited table (the | Task | Location | Notes |
... block) so the table is separated from surrounding content and satisfies
MD058; edit the AGENTS.md section containing that table to insert one empty line
above the table and one empty line below it.
AVALONIA_MIGRATION_CHECKLIST.md-347-384 (1)

347-384: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Insert blank lines before table blocks in Core Features subsections.

At Line 348/362/372/381, tables start immediately after subsection titles and trigger MD058. Add a blank line before each table.

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

In `@AVALONIA_MIGRATION_CHECKLIST.md` around lines 347 - 384, The markdown linter
MD058 is triggered because tables in the "Search & Results", "Window
Management", and "System Integration" subsections start immediately after their
subsection titles; add a single blank line before each table to separate the
heading and the table (i.e., insert a blank line before the table that begins
under the "Search & Results" header, the table under "Window Management", and
the table under "System Integration") so each subsection title is followed by a
blank line then the table.
AVALONIA_MIGRATION_CHECKLIST.md-458-528 (1)

458-528: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language identifiers to fenced code blocks.

The blocks at Line 458 and Line 481 have no language (MD040). Use text (or bash where appropriate).

Suggested patch
-```
+```text
 Flow.Launcher/
 ...

- +text
Flow.Launcher.Avalonia/
...

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

In `@AVALONIA_MIGRATION_CHECKLIST.md` around lines 458 - 528, Update the two
fenced code blocks that list the project trees so they include a language
identifier; specifically, change the triple-backtick fence before the block
starting with "Flow.Launcher/" and the one before the block starting with
"Flow.Launcher.Avalonia/" to use ```text (or ```bash for shell-style listing)
instead of bare ``` so the MD040 lint warning is resolved.
AVALONIA_MIGRATION_CHECKLIST.md-56-166 (1)

56-166: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix single-cell “section header” rows inside 3-column tables.

Rows like Line 58 (| **Startup Section** |) break table column count (MD056). Keep 3 cells for those separator rows.

Suggested patch pattern
-| **Startup Section** |
+| **Startup Section** |  |  |
...
-| **Behavior Section** |
+| **Behavior Section** |  |  |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AVALONIA_MIGRATION_CHECKLIST.md` around lines 56 - 166, The table contains
single-cell section header rows (e.g., "**Startup Section**", "**Behavior
Section**", "**Position Section**", etc.) that break the 3-column table
structure and trigger MD056; update each of those separator rows so they contain
three pipe-separated cells (for example add two empty cells or placeholder text)
to maintain the "| col1 | col2 | col3 |" format throughout the file (search for
rows starting with "| **" and ensure they become three-cell rows).
Flow.Launcher.Avalonia/Views/SettingPages/CustomQueryHotkeyWindow.axaml.cs-81-118 (1)

81-118: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize the validation messages too.

This dialog already routes button text through Translate(...), but the error title/messages are still hard-coded English. That leaves part of the settings flow untranslated on non-English installs.

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

In `@Flow.Launcher.Avalonia/Views/SettingPages/CustomQueryHotkeyWindow.axaml.cs`
around lines 81 - 118, The validation dialog strings in OnDoneClick are
hard-coded; update the calls that pass "Custom Query Hotkey", "Both the hotkey
and query text are required.", and "That hotkey is already assigned to another
custom query." to use the app localization method (Translate) before calling
ShowMessageAsync, and modify ShowMessageAsync to accept already-localized
title/content or call Translate for any non-localized inputs; reference the
OnDoneClick method and ShowMessageAsync to locate and change the message
parameters so all dialog title and content use Translate(...) like the
CloseButtonText does.
Flow.Launcher.Avalonia/Views/Dialogs/PluginUpdateWindow.axaml-40-53 (1)

40-53: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Move the remaining labels through i18n resources.

Restart after updating plugins and Cancel are still hard-coded here, so this dialog won't fully localize with the rest of the Avalonia host.

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

In `@Flow.Launcher.Avalonia/Views/Dialogs/PluginUpdateWindow.axaml` around lines
40 - 53, Replace the two hard-coded UI strings with bindings to the i18n
resource keys so the dialog localizes; specifically change the CheckBox Content
("Restart after updating plugins") to use the localization resource (e.g., a
resource key like RestartAfterUpdatingPlugins) and change the Cancel Button
Content ("Cancel") to use the Cancel resource key, keeping the existing
bindings/properties (Restart, UpdateButtonText) and event handlers
(OnCancelClick, OnUpdateClick) intact so only the Content attributes are
switched to resource-based bindings or StaticResource/Loc markup extensions that
the project uses for localization.
Flow.Launcher.Avalonia/Views/SettingPages/CustomShortcutWindow.axaml.cs-80-100 (1)

80-100: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Trim the shortcut key before validating and saving it.

Right now "foo" and " foo " are treated as different values, so _doesShortcutExist(...) can be bypassed and the saved shortcut key may not expand the way the user expects.

Suggested change
 private async void OnDoneClick(object? sender, RoutedEventArgs e)
 {
-    if (string.IsNullOrWhiteSpace(ShortcutKey) || string.IsNullOrWhiteSpace(ShortcutValue))
+    var normalizedShortcutKey = ShortcutKey.Trim();
+
+    if (string.IsNullOrWhiteSpace(normalizedShortcutKey) || string.IsNullOrWhiteSpace(ShortcutValue))
     {
         await ShowMessageAsync("Custom Shortcut", "Both the shortcut and expansion text are required.");
         return;
     }
 
-    if (((_update && _originalShortcutKey != ShortcutKey) || !_update) && _doesShortcutExist(ShortcutKey))
+    if (((_update && _originalShortcutKey != normalizedShortcutKey) || !_update) &&
+        _doesShortcutExist(normalizedShortcutKey))
     {
         await ShowMessageAsync("Custom Shortcut", "That shortcut key already exists.");
         return;
     }
 
-    if (_update && _originalShortcutKey == ShortcutKey && _originalShortcutValue == ShortcutValue)
+    if (_update && _originalShortcutKey == normalizedShortcutKey && _originalShortcutValue == ShortcutValue)
     {
         Close(false);
         return;
     }
 
+    ShortcutKey = normalizedShortcutKey;
     Close(true);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Flow.Launcher.Avalonia/Views/SettingPages/CustomShortcutWindow.axaml.cs`
around lines 80 - 100, In OnDoneClick, normalize the ShortcutKey by trimming
whitespace before any validation, existence checks, or comparison with
_originalShortcutKey/_originalShortcutValue so leading/trailing spaces don't
bypass _doesShortcutExist or create mismatched saved keys; i.e., compute a
trimmedKey = ShortcutKey?.Trim(), use trimmedKey for the Null/WhiteSpace check,
pass trimmedKey into _doesShortcutExist(trimmedKey), compare trimmedKey to
_originalShortcutKey when determining no-op updates, and ensure the value used
when saving/closing is the trimmed key.
Flow.Launcher.Avalonia/ViewModel/MainViewModel.cs-290-295 (1)

290-295: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

LoadHistory() runs the same history query twice.

After QueryText = string.Empty;, OnQueryTextChanged() already invokes QueryAsync(). The extra _ = QueryAsync(); immediately below repeats the history load every time this view opens.

Suggested change
         if (ActiveView == ActiveView.Results)
         {
             _queryTextBeforeHistory = QueryText;
             ActiveView = ActiveView.History;
             QueryText = string.Empty;
-            _ = QueryAsync();
 
             if (HistoryView.Results.Count > 0)
             {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Flow.Launcher.Avalonia/ViewModel/MainViewModel.cs` around lines 290 - 295,
The history load is triggered twice because setting QueryText = string.Empty
fires OnQueryTextChanged which already calls QueryAsync(), so remove the
redundant explicit call to QueryAsync(); in the MainViewModel method that
switches views (the block referencing ActiveView, _queryTextBeforeHistory,
QueryText and calling QueryAsync()), delete the line "_ = QueryAsync();" so
QueryAsync is only invoked via OnQueryTextChanged when QueryText is set to
empty.
Flow.Launcher.Avalonia/Views/SettingPages/GeneralSettingsPage.axaml-80-100 (1)

80-100: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Move the new search-window labels into localization resources.

Custom screen, Custom position, and Set X and Y when alignment is Custom are user-facing English strings in an otherwise localized page. These should use i18n:Localize keys like the rest of the settings UI.

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

In `@Flow.Launcher.Avalonia/Views/SettingPages/GeneralSettingsPage.axaml` around
lines 80 - 100, Replace the hardcoded English strings in the
SettingsExpanderItem elements with i18n:Localize resource keys: change
Content="Custom screen" to Content="{i18n:Localize CustomScreen}" (or your
chosen key), Content="Custom position" to Content="{i18n:Localize
CustomPosition}", and Description="Set X and Y when alignment is Custom" to
Description="{i18n:Localize CustomPositionDescription}"; add corresponding keys
to the localization resource file so SearchWindowAlign and other localized
entries remain consistent and bind to UI:SettingsExpanderItem and its Footer
ComboBox elements unchanged.
Flow.Launcher.Avalonia/Views/SettingPages/AboutSettingsPage.axaml-44-46 (1)

44-46: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize the new section descriptions.

These descriptions are hardcoded English while the rest of the page is localized, so this page will regress into a mixed-language UI for non-English users. Please move them into the i18n resources as well.

Also applies to: 58-60, 72-74

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

In `@Flow.Launcher.Avalonia/Views/SettingPages/AboutSettingsPage.axaml` around
lines 44 - 46, The Description attributes in AboutSettingsPage.axaml's
SettingsExpander elements (e.g., the one with Header="{i18n:Localize website}"
and IconSource="World") are hardcoded English; replace those literal Description
strings with i18n resource lookups (use i18n:Localize or the existing
localization markup) and add corresponding keys to the i18n resource files; do
the same for the other two expanders referenced (the elements around lines 58-60
and 72-74) so all Header and Description text is pulled from localization keys
rather than hardcoded English.
Flow.Launcher.Avalonia/Views/Dialogs/WelcomeWindow.axaml-50-53 (1)

50-53: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Swap these Page 3 labels or hotkeys.

HotkeyUpDownDesc is currently shown next to left/right arrows, and HotkeyLeftRightDesc is shown next to up/down arrows. The onboarding text is backwards.

Suggested fix
-                        <TextBlock Grid.Row="0" Margin="0,0,0,10" Text="{i18n:Localize HotkeyUpDownDesc}" />
+                        <TextBlock Grid.Row="0" Margin="0,0,0,10" Text="{i18n:Localize HotkeyLeftRightDesc}" />
                         <controls:HotkeyDisplay Grid.Row="0" Grid.Column="1" Keys="←+→" />
-                        <TextBlock Grid.Row="1" Margin="0,0,0,10" Text="{i18n:Localize HotkeyLeftRightDesc}" />
+                        <TextBlock Grid.Row="1" Margin="0,0,0,10" Text="{i18n:Localize HotkeyUpDownDesc}" />
                         <controls:HotkeyDisplay Grid.Row="1" Grid.Column="1" Keys="↑+↓" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Flow.Launcher.Avalonia/Views/Dialogs/WelcomeWindow.axaml` around lines 50 -
53, The two Page 3 items are swapped: the TextBlock using i18n:Localize
HotkeyUpDownDesc is paired with Keys="←+→" and HotkeyLeftRightDesc is paired
with Keys="↑+↓"; swap them so HotkeyUpDownDesc is paired with Keys="↑+↓" and
HotkeyLeftRightDesc is paired with Keys="←+→". Locate the two controls: the
TextBlock elements referencing HotkeyUpDownDesc and HotkeyLeftRightDesc and the
adjacent controls:HotkeyDisplay elements with Keys="←+→" and Keys="↑+↓", and
swap the Keys values or swap the TextBlock positions so each description matches
the corresponding HotkeyDisplay.
Flow.Launcher.Avalonia/Views/SettingPages/HotkeySettingsPage.axaml-102-114 (1)

102-114: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Differentiate the fixed Shift+Enter row from the editable context-menu hotkey.

Both rows render the same label, so users can’t tell which one they can change and which one is hard-coded.

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

In `@Flow.Launcher.Avalonia/Views/SettingPages/HotkeySettingsPage.axaml` around
lines 102 - 114, The two SettingsExpanderItem rows use the same
Content/localization key so users can’t tell the editable hotkey (HotkeyControl
bound to OpenContextMenuHotkey) from the fixed one (HotkeyDisplay
Keys="Shift+Enter"); update the second SettingsExpanderItem to use a different
label (e.g., a new localization key like OpenContextMenuHotkey_Fixed or append
"(fixed)" to the content) or a distinct visual cue so the fixed Shift+Enter row
is clearly labeled as non-editable while leaving the HotkeyControl row
unchanged.
Flow.Launcher.Avalonia/Views/SettingPages/ThemeSettingsPage.axaml-28-40 (1)

28-40: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize the remaining hard-coded labels on this page.

This still exposes English-only UI (Hello there, Theme resources, Sizing and fonts, Theme Builder, Warning, etc.), so the page bypasses the app’s i18n path in visible settings content.

Also applies to: 88-93, 116-116, 201-205, 296-299

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

In `@Flow.Launcher.Avalonia/Views/SettingPages/ThemeSettingsPage.axaml` around
lines 28 - 40, Replace all hard-coded TextBlock/Text content on
ThemeSettingsPage.axaml (e.g., the Text attributes "Hello there", "Sample result
title", "Sample result subtitle" and visible headings like "Theme resources",
"Sizing and fonts", "Theme Builder", "Warning") with localized resource lookups
or bindings to the app's i18n keys (use the same pattern used elsewhere in the
project such as StaticResource/x:Static or a LocalizationManager binding).
Create or use descriptive resource keys (e.g., ThemeSettings_Hello,
ThemeSettings_ResultTitle, ThemeSettings_ResultSubtitle,
ThemeSettings_ThemeResources, ThemeSettings_SizingAndFonts,
ThemeSettings_ThemeBuilder, ThemeSettings_Warning) and replace the literal
strings with those resource references so the page follows the app’s
localization path.
Flow.Launcher.Avalonia/ViewModel/SettingPages/GeneralSettingsViewModel.cs-75-86 (1)

75-86: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

History-style labels won't refresh after a language switch.

UpdateLabels() mutates HistoryStyleOption.Display, but HistoryStyleOption does not raise property changes and the view model never re-raises HistoryStyles/SelectedHistoryStyle. This dropdown will keep the old language until the page is recreated.

Also applies to: 407-423, 809-829

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

In `@Flow.Launcher.Avalonia/ViewModel/SettingPages/GeneralSettingsViewModel.cs`
around lines 75 - 86, UpdateLabels currently mutates HistoryStyleOption.Display
but the UI doesn't refresh because HistoryStyleOption doesn't raise
PropertyChanged and the VM doesn't re-raise HistoryStyles/SelectedHistoryStyle;
fix by making HistoryStyleOption raise INotifyPropertyChanged for its Display
(implement PropertyChanged in UpdateLabel) and/or after calling
HistoryStyles.ForEach(x => x.UpdateLabel(_i18n)) call the view model's
RaisePropertyChanged(nameof(HistoryStyles)) and
RaisePropertyChanged(nameof(SelectedHistoryStyle)) so the dropdown updates when
UpdateLabels() runs.
🧹 Nitpick comments (4)
Flow.Launcher.Avalonia/Views/Dialogs/NotificationWindow.axaml.cs (1)

17-18: 🏗️ Heavy lift

Use CommunityToolkit MVVM primitives instead of manual code-behind state/handlers.

This dialog currently mixes manual INotifyPropertyChanged and click handlers in the view class; please move bindable state/commands to an ObservableObject view model and bind commands from AXAML.

As per coding guidelines: Flow.Launcher.Avalonia/**/*.cs: Avalonia UI code should use CommunityToolkit.Mvvm patterns such as ObservableObject, [ObservableProperty], [RelayCommand].

Also applies to: 48-57, 123-153

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

In `@Flow.Launcher.Avalonia/Views/Dialogs/NotificationWindow.axaml.cs` around
lines 17 - 18, The NotificationWindow class currently implements
INotifyPropertyChanged and contains click handlers; create a
NotificationViewModel that inherits
CommunityToolkit.Mvvm.ComponentModel.ObservableObject, convert window state
fields to [ObservableProperty]s and handlers to [RelayCommand] methods, then set
NotificationWindow.DataContext to an instance of that view model and remove the
manual INotifyPropertyChanged implementation and code-behind click handlers from
NotificationWindow; update AXAML to bind buttons and properties to the view
model commands/properties instead of using code-behind.
Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj (1)

68-68: Add explicit separator in OutputPath glob for consistency across all plugins.

While MSBuild's normalization makes the current pattern $(OutputPath)**\*.* work correctly and deterministically, adding an explicit backslash makes the glob pattern clearer: $(OutputPath)\**\*.*. Apply this consistently across all plugin projects where this pattern is used.

Files affected

All 12 plugin csproj files use this pattern:

  • Flow.Launcher.Plugin.Explorer (line 68)
  • Flow.Launcher.Plugin.WindowsSettings (line 78)
  • Flow.Launcher.Plugin.Sys (line 75)
  • Flow.Launcher.Plugin.Program (line 84)
  • Flow.Launcher.Plugin.ProcessKiller (line 78)
  • Flow.Launcher.Plugin.WebSearch (line 72)
  • Flow.Launcher.Plugin.Url (line 70)
  • Flow.Launcher.Plugin.PluginIndicator (line 69)
  • Flow.Launcher.Plugin.PluginsManager (line 51)
  • Flow.Launcher.Plugin.Shell (line 74)
  • Flow.Launcher.Plugin.BrowserBookmark (line 125)
  • Flow.Launcher.Plugin.Calculator (line 78)
Proposed fix
-      <PluginFiles Include="$(OutputPath)**\*.*" />
+      <PluginFiles Include="$(OutputPath)\**\*.*" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj`
at line 68, Update the PluginFiles Include pattern to explicitly add a directory
separator after $(OutputPath) so the glob is unambiguous: change the attribute
value from "$(OutputPath)**\*.*" to "$(OutputPath)\**\*.*" in this csproj (look
for the PluginFiles Include entry and the OutputPath pattern), and apply the
same edit to the other plugin csproj files listed to ensure consistency across
all plugins.
Flow.Launcher.Avalonia/Views/Dialogs/ProgressBoxWindow.axaml (1)

1-14: ⚡ Quick win

Reorder root AXAML attributes to match repo convention.

x:Class is currently placed after several xmlns/design attributes; please reorder the root attributes to follow the project’s prescribed order for consistent formatting.

As per coding guidelines: **/*.{xaml,axaml}: In XAML/AXAML attribute order: x:Class → xmlns → key/name → layout → size → margin/padding → rest.

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

In `@Flow.Launcher.Avalonia/Views/Dialogs/ProgressBoxWindow.axaml` around lines 1
- 14, Reorder the root Window attributes in ProgressBoxWindow.axaml so x:Class
is the first attribute
(x:Class="Flow.Launcher.Avalonia.Views.Dialogs.ProgressBoxWindow"), followed by
the xmlns declarations, then key/name attributes, layout attributes, size
attributes (Width/Height), margin/padding, and then the remaining attributes
(e.g., CanResize, WindowStartupLocation); update the Window tag for the
ProgressBoxWindow XAML to follow the project's prescribed attribute ordering
convention.
Flow.Launcher.Avalonia/Views/Dialogs/ProgressBoxWindow.axaml.cs (1)

11-25: 🏗️ Heavy lift

Align this dialog with the Avalonia MVVM pattern used in the host.

This window currently acts as its own view-model (INotifyPropertyChanged + code-behind click handlers). Please move state/commands into an ObservableObject view-model and bind commands from AXAML for consistency with the Avalonia migration pattern.

As per coding guidelines: Flow.Launcher.Avalonia/**/*.cs: Avalonia UI code should use CommunityToolkit.Mvvm patterns such as ObservableObject, [ObservableProperty], [RelayCommand].

Also applies to: 125-139

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

In `@Flow.Launcher.Avalonia/Views/Dialogs/ProgressBoxWindow.axaml.cs` around lines
11 - 25, The ProgressBoxWindow class currently implements view-model
responsibilities (implements INotifyPropertyChanged, holds _isIndeterminate,
_progressValue, _progressText and click handlers) — refactor by extracting a new
ViewModel class that derives from
CommunityToolkit.Mvvm.ComponentModel.ObservableObject (e.g.,
ProgressBoxViewModel) and use [ObservableProperty] for IsIndeterminate,
ProgressValue, ProgressText and a [RelayCommand] for the cancel action that
invokes the existing _cancelProgress logic; update ProgressBoxWindow to set
DataContext = new ProgressBoxViewModel(TitleText, cancelAction) and remove
INotifyPropertyChanged implementation and backing fields from the code-behind,
then wire the AXAML to bind to the new ViewModel properties and command names
instead of relying on code-behind handlers (ensure ProgressBoxWindow constructor
still accepts caption and cancelAction and passes them into the ViewModel).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a0986ad8-fa43-42ba-912f-9c97ce418693

📥 Commits

Reviewing files that changed from the base of the PR and between 11eefa4 and 833672b.

📒 Files selected for processing (81)
  • .github/AGENTS.md
  • .github/workflows/dotnet.yml
  • .gitignore
  • .gsd/quick/1-a-review-about-whether-we-have-touched-a/1-SUMMARY.md
  • AGENTS.md
  • AVALONIA_MIGRATION_CHECKLIST.md
  • Flow.Launcher.Avalonia/.agent/README.md
  • Flow.Launcher.Avalonia/AGENTS.md
  • Flow.Launcher.Avalonia/App.axaml.cs
  • Flow.Launcher.Avalonia/AvaloniaPublicAPI.cs
  • Flow.Launcher.Avalonia/Flow.Launcher.Avalonia.csproj
  • Flow.Launcher.Avalonia/Helper/AutoStartup.cs
  • Flow.Launcher.Avalonia/Helper/HotKeyMapper.cs
  • Flow.Launcher.Avalonia/Helper/ResultHelper.cs
  • Flow.Launcher.Avalonia/MainWindow.axaml
  • Flow.Launcher.Avalonia/MainWindow.axaml.cs
  • Flow.Launcher.Avalonia/Storage/History.cs
  • Flow.Launcher.Avalonia/Themes/Base.axaml
  • Flow.Launcher.Avalonia/Themes/Resources.axaml
  • Flow.Launcher.Avalonia/ViewModel/MainViewModel.cs
  • Flow.Launcher.Avalonia/ViewModel/ResultViewModel.cs
  • Flow.Launcher.Avalonia/ViewModel/SettingPages/AboutSettingsViewModel.cs
  • Flow.Launcher.Avalonia/ViewModel/SettingPages/GeneralSettingsViewModel.cs
  • Flow.Launcher.Avalonia/ViewModel/SettingPages/HotkeySettingsViewModel.cs
  • Flow.Launcher.Avalonia/ViewModel/SettingPages/PluginStoreSettingsViewModel.cs
  • Flow.Launcher.Avalonia/ViewModel/SettingPages/ThemeSettingsViewModel.cs
  • Flow.Launcher.Avalonia/Views/Controls/HotkeyControl.axaml.cs
  • Flow.Launcher.Avalonia/Views/Controls/HotkeyDisplay.axaml
  • Flow.Launcher.Avalonia/Views/Controls/HotkeyDisplay.axaml.cs
  • Flow.Launcher.Avalonia/Views/Dialogs/NotificationWindow.axaml
  • Flow.Launcher.Avalonia/Views/Dialogs/NotificationWindow.axaml.cs
  • Flow.Launcher.Avalonia/Views/Dialogs/PluginUpdateWindow.axaml
  • Flow.Launcher.Avalonia/Views/Dialogs/PluginUpdateWindow.axaml.cs
  • Flow.Launcher.Avalonia/Views/Dialogs/ProgressBoxWindow.axaml
  • Flow.Launcher.Avalonia/Views/Dialogs/ProgressBoxWindow.axaml.cs
  • Flow.Launcher.Avalonia/Views/Dialogs/ReportWindow.axaml
  • Flow.Launcher.Avalonia/Views/Dialogs/ReportWindow.axaml.cs
  • Flow.Launcher.Avalonia/Views/Dialogs/WelcomeWindow.axaml
  • Flow.Launcher.Avalonia/Views/Dialogs/WelcomeWindow.axaml.cs
  • Flow.Launcher.Avalonia/Views/ResultListBox.axaml
  • Flow.Launcher.Avalonia/Views/SettingPages/AboutSettingsPage.axaml
  • Flow.Launcher.Avalonia/Views/SettingPages/CustomQueryHotkeyWindow.axaml
  • Flow.Launcher.Avalonia/Views/SettingPages/CustomQueryHotkeyWindow.axaml.cs
  • Flow.Launcher.Avalonia/Views/SettingPages/CustomShortcutWindow.axaml
  • Flow.Launcher.Avalonia/Views/SettingPages/CustomShortcutWindow.axaml.cs
  • Flow.Launcher.Avalonia/Views/SettingPages/GeneralSettingsPage.axaml
  • Flow.Launcher.Avalonia/Views/SettingPages/HotkeySettingsPage.axaml
  • Flow.Launcher.Avalonia/Views/SettingPages/ReleaseNotesWindow.axaml
  • Flow.Launcher.Avalonia/Views/SettingPages/ReleaseNotesWindow.axaml.cs
  • Flow.Launcher.Avalonia/Views/SettingPages/SelectBrowserWindow.axaml
  • Flow.Launcher.Avalonia/Views/SettingPages/SelectBrowserWindow.axaml.cs
  • Flow.Launcher.Avalonia/Views/SettingPages/SelectFileManagerWindow.axaml
  • Flow.Launcher.Avalonia/Views/SettingPages/SelectFileManagerWindow.axaml.cs
  • Flow.Launcher.Avalonia/Views/SettingPages/ThemeSettingsPage.axaml
  • Flow.Launcher.Core/AGENTS.md
  • Flow.Launcher.Core/packages.lock.json
  • Flow.Launcher.Infrastructure/AGENTS.md
  • Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
  • Flow.Launcher.Infrastructure/Storage/JsonStorage.cs
  • Flow.Launcher.Infrastructure/packages.lock.json
  • Flow.Launcher.Plugin/AGENTS.md
  • Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj
  • Flow.Launcher.Plugin/packages.lock.json
  • Flow.Launcher.Test/Avalonia/ProgrammaticQueryFocusRequestTest.cs
  • Flow.Launcher.Test/Flow.Launcher.Test.csproj
  • Flow.Launcher/AGENTS.md
  • Flow.Launcher/Languages/en.xaml
  • Flow.Launcher/Storage/History.cs
  • Flow.Launcher/packages.lock.json
  • Plugins/AGENTS.md
  • Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
  • Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj
  • Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs
  • Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs
  • Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml
  • Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj
  • Plugins/Flow.Launcher.Plugin.ProcessKiller/Views/SettingsControl.xaml
  • Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj
  • Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj
  • Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj
  • opencode.json
✅ Files skipped from review due to trivial changes (7)
  • Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
  • Flow.Launcher.Avalonia/.agent/README.md
  • Flow.Launcher.Avalonia/Flow.Launcher.Avalonia.csproj
  • .gitignore
  • Flow.Launcher.Avalonia/Themes/Base.axaml
  • Flow.Launcher/Languages/en.xaml
  • Flow.Launcher.Plugin/packages.lock.json
🚧 Files skipped from review as they are similar to previous changes (7)
  • Flow.Launcher.Test/Flow.Launcher.Test.csproj
  • Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj
  • .github/workflows/dotnet.yml
  • Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
  • Flow.Launcher.Avalonia/ViewModel/SettingPages/HotkeySettingsViewModel.cs
  • Flow.Launcher.Avalonia/ViewModel/ResultViewModel.cs
  • Flow.Launcher.Avalonia/AvaloniaPublicAPI.cs

Comment thread Flow.Launcher.Avalonia/Helper/AutoStartup.cs
Comment on lines +85 to +89
if (needsRecreation)
{
UnscheduleLogonTask();
ScheduleLogonTask();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t report auto-start as healthy after a failed repair.

Both check paths delete and recreate the startup entry when they see a mismatch, but they ignore the Schedule*/Unschedule* return values and still report success. If the re-registration fails, CheckIsEnabled(...) will treat startup as enabled even though the repair just removed the existing entry.

Suggested fix
                 if (needsRecreation)
                 {
-                    UnscheduleLogonTask();
-                    ScheduleLogonTask();
+                    return UnscheduleLogonTask() && ScheduleLogonTask();
                 }
             }

             return true;
...
             if (!action.Equals(Constant.ExecutablePath, StringComparison.OrdinalIgnoreCase)
                 && !action.Equals($"\"{Constant.ExecutablePath}\"", StringComparison.OrdinalIgnoreCase))
             {
-                UnscheduleRegistry();
-                ScheduleRegistry();
+                return UnscheduleRegistry() && ScheduleRegistry();
             }

             return true;

Also applies to: 115-117

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

In `@Flow.Launcher.Avalonia/Helper/AutoStartup.cs` around lines 85 - 89, The code
currently ignores the return values from UnscheduleLogonTask() and
ScheduleLogonTask() when needsRecreation is true and still reports success;
modify the logic in the block that uses needsRecreation (and the similar block
at lines 115-117) to check the boolean results from UnscheduleLogonTask() and
ScheduleLogonTask(), and only consider the repair successful if both calls
return true (or propagate failure accordingly); if either call fails, ensure
CheckIsEnabled(...) will not treat auto-start as healthy (e.g., set the
status/flag to false or return failure) and log the failure so callers know the
re-registration failed.

Comment on lines +52 to +79
internal static void SetToggleHotkey(string hotkeyString)
{
RemoveToggleHotkey();

if (string.IsNullOrWhiteSpace(hotkeyString))
{
Log.Warn(ClassName, "Empty hotkey string");
return;
}

var (mods, key) = GlobalHotkey.ParseHotkeyString(hotkeyString);

if (key == 0)
{
Log.Error(ClassName, $"Failed to parse hotkey: {hotkeyString}");
return;
}

_toggleHotkeyId = GlobalHotkey.Register(mods, key, OnToggleHotkey);

if (_toggleHotkeyId < 0)
{
Log.Error(ClassName, $"Failed to register hotkey: {hotkeyString}");
}
else
{
Log.Info(ClassName, $"Registered toggle hotkey: {hotkeyString}");
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep the current toggle hotkey alive until the replacement succeeds.

SetToggleHotkey() unregisters the existing toggle hotkey before it knows the new value parses and registers cleanly. Any bad or unavailable value leaves the launcher without its main global hotkey.

A safer flow here is: validate/attempt the new registration first, then swap registrations only after success, or roll back to the previous registration if the new one fails.

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

In `@Flow.Launcher.Avalonia/Helper/HotKeyMapper.cs` around lines 52 - 79,
SetToggleHotkey currently removes the existing hotkey before
validating/registering the new one; change the flow so the current hotkey is
kept until the new hotkey is proven valid and registered. In SetToggleHotkey use
GlobalHotkey.ParseHotkeyString to validate first and then attempt
GlobalHotkey.Register(mods, key, OnToggleHotkey) into a temporary id; if parse
fails (key == 0) or the temporary register returns < 0, log the error and return
without calling RemoveToggleHotkey or changing _toggleHotkeyId; if the temporary
register succeeds, then call RemoveToggleHotkey (to clear the old
_toggleHotkeyId), assign _toggleHotkeyId = tempId and log success. Ensure all
references use SetToggleHotkey, GlobalHotkey.ParseHotkeyString,
GlobalHotkey.Register, RemoveToggleHotkey, _toggleHotkeyId, and OnToggleHotkey.

Comment on lines +26 to +39
if (LastOpenedHistoryItems.Count >= _maxHistory)
{
LastOpenedHistoryItems.RemoveAt(0);
}

var existingHistoryItem = LastOpenedHistoryItems.FirstOrDefault(x => x.Equals(queryText, result));
if (existingHistoryItem is not null)
{
existingHistoryItem.ExecutedDateTime = DateTime.Now;
existingHistoryItem.Query = queryText;
return;
}

LastOpenedHistoryItems.Add(new LastOpenedHistoryResult(queryText, result));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reorder capacity trimming to avoid dropping entries on updates.

Line 26 trims first, then Line 31 checks for existing entries. At max size, updating an existing history row still removes the oldest item, causing unintended data loss.

Suggested fix
-        if (LastOpenedHistoryItems.Count >= _maxHistory)
-        {
-            LastOpenedHistoryItems.RemoveAt(0);
-        }
-
         var existingHistoryItem = LastOpenedHistoryItems.FirstOrDefault(x => x.Equals(queryText, result));
         if (existingHistoryItem is not null)
         {
             existingHistoryItem.ExecutedDateTime = DateTime.Now;
             existingHistoryItem.Query = queryText;
             return;
         }
+
+        if (LastOpenedHistoryItems.Count >= _maxHistory)
+        {
+            LastOpenedHistoryItems.RemoveAt(0);
+        }

         LastOpenedHistoryItems.Add(new LastOpenedHistoryResult(queryText, result));
📝 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
if (LastOpenedHistoryItems.Count >= _maxHistory)
{
LastOpenedHistoryItems.RemoveAt(0);
}
var existingHistoryItem = LastOpenedHistoryItems.FirstOrDefault(x => x.Equals(queryText, result));
if (existingHistoryItem is not null)
{
existingHistoryItem.ExecutedDateTime = DateTime.Now;
existingHistoryItem.Query = queryText;
return;
}
LastOpenedHistoryItems.Add(new LastOpenedHistoryResult(queryText, result));
var existingHistoryItem = LastOpenedHistoryItems.FirstOrDefault(x => x.Equals(queryText, result));
if (existingHistoryItem is not null)
{
existingHistoryItem.ExecutedDateTime = DateTime.Now;
existingHistoryItem.Query = queryText;
return;
}
if (LastOpenedHistoryItems.Count >= _maxHistory)
{
LastOpenedHistoryItems.RemoveAt(0);
}
LastOpenedHistoryItems.Add(new LastOpenedHistoryResult(queryText, result));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Flow.Launcher.Avalonia/Storage/History.cs` around lines 26 - 39, The capacity
trim runs before checking for an existing entry which causes removal of the
oldest item when an update should have occurred; change the logic in the method
using LastOpenedHistoryItems and _maxHistory so you first search for
existingHistoryItem (using LastOpenedHistoryItems.FirstOrDefault(...)), update
its ExecutedDateTime and Query and return if found, and only when adding a new
LastOpenedHistoryResult(queryText, result) perform the size check and remove the
oldest item (RemoveAt(0)) if LastOpenedHistoryItems.Count >= _maxHistory before
calling Add.

Comment on lines +228 to +235
public void OnPluginsReady()
{
_pluginsReady = true;
MainWindowVisibility = true;
Log.Info(ClassName, "Plugins ready - window shown");
if (!string.IsNullOrWhiteSpace(QueryText))
_ = QueryAsync();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't force the launcher visible when plugins finish loading.

OnPluginsReady() flips MainWindowVisibility to true unconditionally, so a background/auto-start launch will surface the main window as soon as plugin init completes. This should only resume a pending query; visibility still needs to come from the normal show/hotkey flow.

Suggested change
 public void OnPluginsReady()
 {
     _pluginsReady = true;
-    MainWindowVisibility = true;
-    Log.Info(ClassName, "Plugins ready - window shown");
+    Log.Info(ClassName, "Plugins ready");
     if (!string.IsNullOrWhiteSpace(QueryText))
+    {
         _ = QueryAsync();
+    }
 }
📝 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 OnPluginsReady()
{
_pluginsReady = true;
MainWindowVisibility = true;
Log.Info(ClassName, "Plugins ready - window shown");
if (!string.IsNullOrWhiteSpace(QueryText))
_ = QueryAsync();
}
public void OnPluginsReady()
{
_pluginsReady = true;
Log.Info(ClassName, "Plugins ready");
if (!string.IsNullOrWhiteSpace(QueryText))
{
_ = QueryAsync();
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Flow.Launcher.Avalonia/ViewModel/MainViewModel.cs` around lines 228 - 235,
OnPluginsReady currently forces the UI visible by setting MainWindowVisibility =
true; remove that line so plugin initialization does not surface the window
automatically. Keep setting _pluginsReady = true and the Log.Info(ClassName,
"Plugins ready - window shown") message should be updated or left as
appropriate, and preserve the pending-query behavior by calling QueryAsync()
when QueryText is non-empty (i.e., keep the existing _ = QueryAsync() block).
Ensure the method only resumes a pending query and does not change
MainWindowVisibility; reference: OnPluginsReady, MainWindowVisibility,
QueryAsync.

Comment on lines +47 to +55
private void OnOpenIssueClick(object? sender, RoutedEventArgs e)
{
Process.Start(new ProcessStartInfo(IssueUrl) { UseShellExecute = true });
}

private void OnOpenLogsClick(object? sender, RoutedEventArgs e)
{
Process.Start(new ProcessStartInfo(DataLocation.VersionLogDirectory) { UseShellExecute = true });
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle shell launch failures in dialog actions.

Process.Start on Line 49 and Line 54 can throw and crash this crash-reporting window path. Wrap these opens with safe error handling.

Suggested fix pattern
 private void OnOpenIssueClick(object? sender, RoutedEventArgs e)
 {
-    Process.Start(new ProcessStartInfo(IssueUrl) { UseShellExecute = true });
+    TryOpenWithShell(IssueUrl);
 }

 private void OnOpenLogsClick(object? sender, RoutedEventArgs e)
 {
-    Process.Start(new ProcessStartInfo(DataLocation.VersionLogDirectory) { UseShellExecute = true });
+    TryOpenWithShell(DataLocation.VersionLogDirectory);
 }
+
+private static void TryOpenWithShell(string target)
+{
+    try
+    {
+        Process.Start(new ProcessStartInfo(target) { UseShellExecute = true });
+    }
+    catch
+    {
+        // keep report window stable even if shell open fails
+    }
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Flow.Launcher.Avalonia/Views/Dialogs/ReportWindow.axaml.cs` around lines 47 -
55, OnOpenIssueClick and OnOpenLogsClick call Process.Start directly which can
throw and crash the dialog; wrap the Process.Start calls in try/catch in both
methods (OnOpenIssueClick and OnOpenLogsClick), use the same
ProcessStartInfo(IssueUrl) and
ProcessStartInfo(DataLocation.VersionLogDirectory) respectively, and on
exception handle it safely by logging the exception (via your existing logger)
and showing a non-fatal user-facing error message (e.g., message box) so the
crash-reporting window does not crash.

Comment on lines +69 to +73
var website = pluginException.Metadata.Website;
if (!website.StartsWith("https://github.com", StringComparison.OrdinalIgnoreCase))
{
return website;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard Website before calling StartsWith.

Line 70 dereferences website without a null/empty check. Plugin metadata can be missing this field, which would throw while opening the report window.

Suggested fix
 var website = pluginException.Metadata.Website;
+if (string.IsNullOrWhiteSpace(website))
+{
+    return Constant.IssuesUrl;
+}
+
 if (!website.StartsWith("https://github.com", StringComparison.OrdinalIgnoreCase))
 {
     return website;
 }
📝 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
var website = pluginException.Metadata.Website;
if (!website.StartsWith("https://github.com", StringComparison.OrdinalIgnoreCase))
{
return website;
}
var website = pluginException.Metadata.Website;
if (string.IsNullOrWhiteSpace(website))
{
return Constant.IssuesUrl;
}
if (!website.StartsWith("https://github.com", StringComparison.OrdinalIgnoreCase))
{
return website;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Flow.Launcher.Avalonia/Views/Dialogs/ReportWindow.axaml.cs` around lines 69 -
73, Guard against null/empty before calling StartsWith on
pluginException.Metadata.Website: check string.IsNullOrEmpty (or
IsNullOrWhiteSpace) for the local variable website and return website (or an
appropriate default) immediately if it's null/empty, then proceed to call
website.StartsWith("https://github.com", StringComparison.OrdinalIgnoreCase);
update the block that currently reads var website =
pluginException.Metadata.Website; if (!website.StartsWith(...)) { return
website; } to perform the null/empty guard first so you never dereference
website.

Comment on lines +27 to +28
CustomBrowsers = new ObservableCollection<CustomBrowserViewModel>(_settings.CustomBrowserList.Select(x => x.Copy()));
_selectedCustomBrowserIndex = Math.Clamp(_settings.CustomBrowserIndex, 0, Math.Max(0, CustomBrowsers.Count - 1));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard the empty-profile case.

Line 54 assumes at least one browser exists, but Lines 112-113 can reduce CustomBrowsers to zero and then reclamp the index back to 0. After that, any binding through CurrentBrowser will hit CustomBrowsers[0], and Line 161 persists an invalid selected index.

Please either prevent deleting the last entry or model “no selection” explicitly with -1/nullable current item before wiring bindings through CurrentBrowser.

Also applies to: 54-54, 112-113, 160-161

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

In `@Flow.Launcher.Avalonia/Views/SettingPages/SelectBrowserWindow.axaml.cs`
around lines 27 - 28, The code currently assumes at least one browser in
CustomBrowsers and clamps _selectedCustomBrowserIndex to 0, causing
out-of-range/bad persistence when the list becomes empty; update the logic in
the SelectBrowserWindow to model “no selection” explicitly (use -1 or nullable
index) or prevent removing the last entry: change initialization of
_selectedCustomBrowserIndex (from Math.Clamp(...)) to allow -1 when
CustomBrowsers.Count == 0, update CurrentBrowser to return null when index is
-1, update delete/remove logic that modifies CustomBrowsers (the code around the
handlers at lines referenced) to either block removal if Count == 1 or set
_selectedCustomBrowserIndex = -1 after removal, and ensure when persisting back
to _settings.CustomBrowserIndex you write a sentinel (e.g., -1) or skip
persistence for no-selection so you never persist an invalid index.

Comment on lines +29 to +30
CustomExplorers = new ObservableCollection<CustomExplorerViewModel>(_settings.CustomExplorerList.Select(x => x.Copy()));
_selectedCustomExplorerIndex = Math.Clamp(_settings.CustomExplorerIndex, 0, Math.Max(0, CustomExplorers.Count - 1));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle CustomExplorers == 0 before dereferencing CurrentExplorer.

The same index-clamping pattern here leaves SelectedCustomExplorerIndex == 0 even when the collection is empty. That makes Line 56 and Line 58 unsafe after deleting the last profile, and Lines 143-144 then save an invalid selection back to settings.

Please keep at least one entry around or switch this window to a proper “no selection” state before binding through CurrentExplorer/CanSave.

Also applies to: 56-60, 84-85, 143-144

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

In `@Flow.Launcher.Avalonia/Views/SettingPages/SelectFileManagerWindow.axaml.cs`
around lines 29 - 30, The SelectedCustomExplorerIndex is clamped to 0 even when
CustomExplorers is empty, causing dereferences of CurrentExplorer/CanSave and
saving an invalid selection; change the selection logic to support "no
selection" by making the index nullable (or allow -1) and when constructing
CustomExplorers from _settings.CustomExplorerList set
_selectedCustomExplorerIndex = CustomExplorers.Count > 0 ?
Math.Clamp(_settings.CustomExplorerIndex, 0, CustomExplorers.Count - 1) : -1 (or
null), update SelectedCustomExplorerIndex property and any consumers
(CurrentExplorer, CanSave, save logic) to handle a missing selection (return
null or false and disable save), and ensure when deleting the last profile you
either add a default fallback CustomExplorerViewModel to CustomExplorers or set
the index to -1/null and prevent writing an invalid index back to
_settings.CustomExplorerIndex.

</ui:SettingsExpanderItem>
</ui:SettingsExpander>

<ui:SettingsExpander Header="{i18n:Localize Clock} / {i18n:Localize Date}"
Copy link
Copy Markdown
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:

Does Avalonia AXAML support concatenating multiple markup extensions inside one attribute value, such as Header="{i18n:Localize Clock} / {i18n:Localize Date}"?

💡 Result:

No—Avalonia AXAML does not support concatenating multiple markup extension results by placing multiple markup extensions directly inside a single attribute value like: Header="{i18n:Localize Clock} / {i18n:Localize Date}". In Avalonia, a markup extension is a special expression enclosed in a single pair of curly braces. While Avalonia does support nesting one markup extension inside another (e.g., Binding with Converter={StaticResource ...}), the docs only describe markup-extension nesting as a structured feature, not free-form concatenation of multiple independent {…} expressions within the same attribute value. Practical alternatives: - Use a single localization/formatting mechanism that returns the whole header string (e.g., one markup extension call that formats both parts). - Or use a binding-based approach that can combine values, such as MultiBinding with a converter, then apply the localized pieces inside that converter.

Citations:


Don't concatenate multiple markup extensions inside one Header attribute value.

Header="{i18n:Localize Clock} / {i18n:Localize Date}" is invalid—Avalonia AXAML does not support multiple markup extensions in a single attribute. Use a single localization key for the combined label, or compose the header with child content using a MultiBinding approach with a converter.

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

In `@Flow.Launcher.Avalonia/Views/SettingPages/ThemeSettingsPage.axaml` at line
211, The Header on the SettingsExpander is using two markup extensions
concatenated ("Header=\"{i18n:Localize Clock} / {i18n:Localize Date}\"") which
Avalonia AXAML doesn't support; fix it by using a single localization key for
the combined label (add a "ClockDate" entry and use Header="{i18n:Localize
ClockDate}") or replace the Header attribute with child content (e.g., set
Header to a container element and place two localized TextBlocks or use a
MultiBinding+converter) — update the SettingsExpander element's Header usage
accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants