mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
11 KiB
11 KiB
AGENTS.md - Flow.Launcher
This document provides essential information for AI agents working on the Flow.Launcher codebase.
Project Overview
Flow.Launcher is a Windows productivity launcher (similar to Alfred/Raycast) built with:
- WPF (original UI framework) -
Flow.Launcher/ - Avalonia (migration in progress ~35-40%) -
Flow.Launcher.Avalonia/ - .NET 9.0 targeting
net9.0-windows10.0.19041.0 - CommunityToolkit.Mvvm for MVVM patterns
- FluentAvalonia for modern UI in Avalonia version
The codebase is actively being migrated from WPF to Avalonia. See AVALONIA_MIGRATION_CHECKLIST.md for detailed progress.
Essential Commands
Build
# Build entire solution
dotnet build
# Build specific project
dotnet build Flow.Launcher.Avalonia/Flow.Launcher.Avalonia.csproj
# Release build
dotnet build -c Release
# Restore dependencies (required for CI)
nuget restore
Test
# Run all tests
dotnet test
# Run tests with verbosity
dotnet test --verbosity normal
# Run specific test file/class
dotnet test --filter "ClassName=FuzzyMatcherTest"
Run
# Run WPF version
./Output/Debug/Flow.Launcher.exe
# Run Avalonia version
./Output/Debug/Avalonia/Flow.Launcher.Avalonia.exe
Output Locations
| Configuration | WPF Output | Avalonia Output |
|---|---|---|
| Debug | Output/Debug/ |
Output/Debug/Avalonia/ |
| Release | Output/Release/ |
Output/Release/Avalonia/ |
Project Structure
Flow.Launcher/
├── Flow.Launcher/ # WPF main application
│ ├── MainWindow.xaml # Main search window
│ ├── SettingWindow.xaml # Settings window
│ ├── ViewModel/ # ViewModels (MVVM)
│ ├── SettingPages/ # Settings page views/viewmodels
│ ├── Helper/ # Utility classes
│ ├── Converters/ # XAML value converters
│ ├── Themes/ # Theme XAML files
│ ├── Languages/ # Localization (*.xaml)
│ └── Resources/ # Icons, fonts, styles
│
├── Flow.Launcher.Avalonia/ # Avalonia main application (migration)
│ ├── MainWindow.axaml # Main search window
│ ├── Views/ # Avalonia views
│ │ ├── SettingPages/ # Settings pages
│ │ └── Controls/ # Custom controls
│ ├── ViewModel/ # ViewModels
│ ├── Helper/ # Utilities
│ ├── Converters/ # Avalonia converters
│ └── Themes/ # Avalonia themes
│
├── Flow.Launcher.Plugin/ # Plugin SDK (shared)
│ ├── Interfaces/ # IPlugin, IPublicAPI, etc.
│ ├── Result.cs # Search result model
│ ├── Query.cs # Query model
│ └── PluginMetadata.cs # Plugin metadata
│
├── Flow.Launcher.Infrastructure/ # Shared infrastructure
│ ├── UserSettings/ # Settings models
│ ├── StringMatcher.cs # Fuzzy search algorithm
│ └── Logger/ # Logging utilities
│
├── Flow.Launcher.Core/ # Core business logic
│ ├── Plugin/ # Plugin management
│ │ └── PluginManager.cs # Plugin lifecycle
│ ├── Resource/ # Internationalization
│ └── ExternalPlugins/ # Plugin store
│
├── Plugins/ # Built-in plugins
│ ├── Flow.Launcher.Plugin.Calculator/
│ ├── Flow.Launcher.Plugin.Explorer/
│ ├── Flow.Launcher.Plugin.Program/
│ ├── Flow.Launcher.Plugin.WebSearch/
│ └── ... (10+ plugins)
│
├── Flow.Launcher.Test/ # Unit tests (NUnit)
│
└── Scripts/ # Build scripts
└── post_build.ps1 # Packaging script
Code Conventions
Naming
- PascalCase for public members, types, properties, methods
- camelCase for local variables, parameters
- _camelCase for private fields (underscore prefix)
- UPPER_CASE constants are PascalCase per
.editorconfig - No
this.qualifier (per.editorconfig)
C# Style
// File-scoped namespaces preferred
namespace Flow.Launcher.ViewModel;
// Prefer var when type is apparent
var results = new List<Result>();
// Braces always required
if (condition)
{
DoSomething();
}
// Allman brace style (new line before opening brace)
public void Method()
{
// ...
}
// 4-space indentation for code
// 2-space indentation for XML/XAML
MVVM Patterns
The project uses CommunityToolkit.Mvvm with source generators:
// ViewModels use ObservableObject base
public partial class MainViewModel : ObservableObject
{
// [ObservableProperty] generates property + change notification
[ObservableProperty]
private string _queryText = string.Empty;
// [RelayCommand] generates ICommand implementation
[RelayCommand]
private void Search() { ... }
}
// WPF uses BaseModel which wraps INotifyPropertyChanged
public class MainViewModel : BaseModel
{
public string QueryText
{
get => _queryText;
set
{
if (_queryText != value)
{
_queryText = value;
OnPropertyChanged();
}
}
}
}
XAML Style
Uses XamlStyler with specific rules (see Settings.XamlStyler):
- One attribute per line (except when ≤2)
- Specific attribute ordering (x:Class first, then xmlns, etc.)
- Space before closing slash:
<Element />
WPF XAML (.xaml):
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
Avalonia AXAML (.axaml):
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="using:FluentAvalonia.UI.Controls">
Plugin Architecture
Plugin Interface
All plugins implement IPlugin or IAsyncPlugin:
public interface IPlugin : IAsyncPlugin
{
List<Result> Query(Query query);
void Init(PluginInitContext context);
}
public interface IAsyncPlugin
{
Task<List<Result>> QueryAsync(Query query, CancellationToken token);
Task InitAsync(PluginInitContext context);
}
Creating Results
return new List<Result>
{
new Result
{
Title = "Result Title",
SubTitle = "Optional subtitle",
IcoPath = "Images/icon.png", // Relative to plugin directory
Score = 100, // Higher = better match
Action = context =>
{
// Execute action, return true to hide window
return true;
}
}
};
Plugin Metadata
Each plugin needs plugin.json:
{
"ID": "unique-guid",
"ActionKeyword": "keyword",
"Name": "Plugin Name",
"Description": "Description",
"Author": "Author",
"Version": "1.0.0",
"Language": "csharp",
"Website": "https://...",
"IcoPath": "Images\\icon.png",
"ExecuteFileName": "Plugin.dll"
}
Plugin Settings
// Load settings
var settings = context.API.LoadSettingJsonStorage<MySettings>();
// Save (automatic on app close, or manual)
context.API.SaveSettingJsonStorage<MySettings>();
Key Classes & Files
| Class/File | Purpose |
|---|---|
MainViewModel.cs |
Main search window logic |
ResultsViewModel.cs |
Search results management |
Settings.cs |
User settings model |
PluginManager.cs |
Plugin lifecycle management |
StringMatcher.cs |
Fuzzy search algorithm |
IPublicAPI.cs |
Plugin API interface |
Result.cs |
Search result model |
Query.cs |
Search query model |
Testing
- Framework: NUnit 4.x
- Mocking: Moq
- Test file naming:
*Test.cs - Test class attribute:
[TestFixture]
[TestFixture]
public class FuzzyMatcherTest
{
[Test]
public void WhenSearching_ThenReturnsExpectedResults()
{
var matcher = new StringMatcher(null);
var result = matcher.FuzzyMatch("chr", "Chrome");
ClassicAssert.IsTrue(result.RawScore > 0);
}
[TestCase("chrome")]
[TestCase("chr")]
public void ParameterizedTest(string query)
{
// ...
}
}
Avalonia Migration Notes
When working on the Avalonia migration:
- File extensions: Use
.axamlnot.xamlfor Avalonia - Namespace: Use
xmlns="https://github.com/avaloniaui" - Controls: Use FluentAvalonia
ui:SettingsExpanderfor settings pages - Visibility: Use
IsVisiblenotVisibility(noCollapsedenum) - Converters: Different converter approach (see
Converters/) - Define directive:
#if AVALONIAfor conditional compilation
Key differences:
<!-- WPF -->
<Button Visibility="{Binding IsVisible, Converter={StaticResource BoolToVisibility}}" />
<!-- Avalonia -->
<Button IsVisible="{Binding IsVisible}" />
Localization
Resources are in Languages/*.xaml files:
<!-- Languages/en.xaml -->
<sys:String x:Key="startFlowLauncherOnSystemStartup">Start Flow Launcher on system startup</sys:String>
Usage in XAML:
<!-- WPF -->
<TextBlock Text="{DynamicResource startFlowLauncherOnSystemStartup}" />
<!-- Avalonia (custom extension) -->
<TextBlock Text="{i18n:Localize startFlowLauncherOnSystemStartup}" />
Common Tasks
Adding a new setting
- Add property to
Flow.Launcher.Infrastructure/UserSettings/Settings.cs - Add UI in appropriate settings page (WPF and/or Avalonia)
- Bind to ViewModel property
Adding a new plugin
- Create project under
Plugins/folder - Reference
Flow.Launcher.Plugin - Implement
IPluginorIAsyncPlugin - Add
plugin.jsonmetadata - Add to solution and build dependencies in WPF project
Fixing a ViewModel binding
- Ensure property raises
PropertyChangedor uses[ObservableProperty] - Check DataContext is set correctly
- Verify binding path matches property name exactly
Gotchas & Tips
- Build order matters: WPF project depends on plugins being built first
- Kill running instance: Build kills running
Flow.Launcher.exeautomatically - Plugin isolation: Plugins run in separate app domains, can't share state directly
- Settings persist: Changes to
Settings.csproperties auto-save via Fody PropertyChanged - Windows Search: Some tests require Windows Search service (
WSearch) to be running - Nullable: Avalonia project has
<Nullable>enable</Nullable>, WPF does not - Framework reference: Avalonia still references WPF assemblies for
IPublicAPIcompatibility
CI/CD
GitHub Actions workflow (.github/workflows/dotnet.yml):
- Runs on Windows
- Uses .NET 9.0
- Builds Release configuration
- Runs tests
- Creates installer via Squirrel
Key environment variables:
FlowVersion: Version number (e.g., "1.20.2")BUILD_NUMBER: CI build number
Resources
- Migration checklist:
AVALONIA_MIGRATION_CHECKLIST.md - Plugin SDK docs:
Flow.Launcher.Plugin/README.md - EditorConfig:
.editorconfigfor code style - XAML formatting:
Settings.XamlStyler