diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..89806d707 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,430 @@ +# 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 + +```bash +# 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 + +```bash +# Run all tests +dotnet test + +# Run tests with verbosity +dotnet test --verbosity normal + +# Run specific test file/class +dotnet test --filter "ClassName=FuzzyMatcherTest" +``` + +### Run + +```bash +# 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 + +```csharp +// File-scoped namespaces preferred +namespace Flow.Launcher.ViewModel; + +// Prefer var when type is apparent +var results = new List(); + +// 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: + +```csharp +// 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: `` + +**WPF XAML** (`.xaml`): +```xml + +``` + +**Avalonia AXAML** (`.axaml`): +```xml + +``` + +--- + +## Plugin Architecture + +### Plugin Interface + +All plugins implement `IPlugin` or `IAsyncPlugin`: + +```csharp +public interface IPlugin : IAsyncPlugin +{ + List Query(Query query); + void Init(PluginInitContext context); +} + +public interface IAsyncPlugin +{ + Task> QueryAsync(Query query, CancellationToken token); + Task InitAsync(PluginInitContext context); +} +``` + +### Creating Results + +```csharp +return new List +{ + 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`: +```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 + +```csharp +// Load settings +var settings = context.API.LoadSettingJsonStorage(); + +// Save (automatic on app close, or manual) +context.API.SaveSettingJsonStorage(); +``` + +--- + +## 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]` + +```csharp +[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: + +1. **File extensions**: Use `.axaml` not `.xaml` for Avalonia +2. **Namespace**: Use `xmlns="https://github.com/avaloniaui"` +3. **Controls**: Use FluentAvalonia `ui:SettingsExpander` for settings pages +4. **Visibility**: Use `IsVisible` not `Visibility` (no `Collapsed` enum) +5. **Converters**: Different converter approach (see `Converters/`) +6. **Define directive**: `#if AVALONIA` for conditional compilation + +**Key differences**: +```xml + + - - - - - - - + diff --git a/Flow.Launcher.Avalonia/WpfResources/CustomControlTemplate.xaml b/Flow.Launcher.Avalonia/WpfResources/CustomControlTemplate.xaml new file mode 100644 index 000000000..37b85b970 --- /dev/null +++ b/Flow.Launcher.Avalonia/WpfResources/CustomControlTemplate.xaml @@ -0,0 +1,6102 @@ + + + + Segoe UI + + + + + + {DynamicResource SettingWindowFont} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0,0,0,4 + 12,6,0,6 + 11,5,32,6 + 32 + + + + + + + + + + + + + + + 0,0,0,4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +