{"id":35425472,"url":"https://github.com/jamesericwong/switchblade","last_synced_at":"2026-04-02T18:41:14.958Z","repository":{"id":331494438,"uuid":"1126359590","full_name":"jamesericwong/switchblade","owner":"jamesericwong","description":"SwitchBlade is a high-performance, keyboard-driven window switcher for Windows built with .NET 9 and WPF. It features an extensible plugin system that indexes both top-level applications and internal tabs (Chrome, Edge, Terminal, Notepad++), providing lightning-fast, zero-allocation search and navigation for power users.","archived":false,"fork":false,"pushed_at":"2026-03-30T00:32:30.000Z","size":7065,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-30T03:54:57.340Z","etag":null,"topics":["csharp","dotnet","dotnet-9","keyboard-shortcuts","mvvm","navigation","perf-optimization","plugin-system","productivity","tab-management","ui-automation","win32-api","window-switcher","windows","wpf","zero-allocation"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jamesericwong.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-01T18:34:37.000Z","updated_at":"2026-03-30T00:31:00.000Z","dependencies_parsed_at":"2026-03-30T03:00:24.305Z","dependency_job_id":null,"html_url":"https://github.com/jamesericwong/switchblade","commit_stats":null,"previous_names":["jamesericwong/switchblade"],"tags_count":90,"template":false,"template_full_name":null,"purl":"pkg:github/jamesericwong/switchblade","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesericwong%2Fswitchblade","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesericwong%2Fswitchblade/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesericwong%2Fswitchblade/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesericwong%2Fswitchblade/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jamesericwong","download_url":"https://codeload.github.com/jamesericwong/switchblade/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesericwong%2Fswitchblade/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31313178,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","dotnet","dotnet-9","keyboard-shortcuts","mvvm","navigation","perf-optimization","plugin-system","productivity","tab-management","ui-automation","win32-api","window-switcher","windows","wpf","zero-allocation"],"created_at":"2026-01-02T18:19:38.104Z","updated_at":"2026-04-02T18:41:14.945Z","avatar_url":"https://github.com/jamesericwong.png","language":"C#","readme":"# SwitchBlade Technical Documentation\n\n**Current Version: 1.9.16**\n\n[![Coverage Status](https://jamesericwong.github.io/switchblade/badge_linecoverage.svg)](https://jamesericwong.github.io/switchblade/)\n\n## 📚 Documentation\n\n| Document | Description |\n|----------|-------------|\n| [CHANGELOG.md](CHANGELOG.md) | Version history and release notes |\n| [PLUGIN_DEVELOPMENT.md](PLUGIN_DEVELOPMENT.md) | Guide for creating custom plugins |\n| [BUILD.md](BUILD.md) | Build instructions and project setup |\n\n---\n\n## Overview\nSwitchBlade is a high-performance Keyboard-Driven Window Switcher for Windows. It is built using **C# / WPF** and follows the **MVVM (Model-View-ViewModel)** architectural pattern. It is designed to be extensible via a robust Plugin System, allowing it to index not just top-level windows but also internal document tabs as searchable items.\n\nThe package comes with several specialized plugins out of the box:\n- **Window Finder**: Discovers all standard top-level desktop application windows.\n- **Chrome Tab Finder**: Indexes individual tabs from Google Chrome, Microsoft Edge, and other Chromium-based browsers.\n- **Windows Terminal**: Discovers and allows switching between multiple tabs within Windows Terminal instances.\n- **Notepad++**: Indexes and switches between individual open files/tabs in Notepad++.\n- **Microsoft Teams**: Discovers and switches between individual chat conversations in Microsoft Teams (v2).\n\n\n## Basic Usage\n\n- **Toggle SwitchBlade**: Press `Ctrl + Shift + Q` (Default) to show or hide the search window.\n- **Search**: Start typing immediately to filter open windows and tabs.\n- **Navigate**: Use `Up` / `Down` arrows to select a window.\n- **Activate**: Press `Enter` to switch to the selected window.\n- **Close**: Press `Escape` to hide SwitchBlade without switching.\n\n## Keyboard Shortcuts\n\nSwitchBlade supports the following keyboard shortcuts for navigation:\n\n| Key | Action |\n| :--- | :--- |\n| `↑` / `↓` | Move selection up/down by one item |\n| `Page Up` / `Page Down` | Move selection up/down by one visible page |\n| `Ctrl+Home` | Jump to the first item |\n| `Ctrl+End` | Jump to the last item |\n| `Enter` | Activate the selected window |\n| `Escape` | Hide the SwitchBlade window |\n| `Alt+1` to `Alt+0` | Quick-switch to windows 1-10 (configurable modifier) |\n\n## Architecture\n\n### Core Components\n- **MainViewModel**: The central brain of the application. It orchestrates window provider execution, aggregates results, and manages the search/filter state.\n- **Service Layer**: \n  - `SettingsService`: Manages persistence of user preferences (Registry-based).\n  - `HotKeyService`: Handles global low-level keyboard hooks for the toggle hotkey.\n  - `WindowOrchestrationService`: Coordinates window discovery, reconciliation, and provider result aggregation.\n  - `UiaElementResolver` (Contracts): Shared 3-stage HWND→FindFirst→TreeWalker fallback for UIA plugins.\n- **Window Providers**: Independent modules responsible for scanning and returning `WindowItem` objects.\n\n```mermaid\ngraph TD\n    User((User)) --\u003e|Hotkey| HotKeyService\n    User --\u003e|Types| SearchText[Search Input]\n    SearchText --\u003e|Triggers| MainViewModel\n    HotKeyService --\u003e|Toggle| MainViewModel\n    \n    subgraph Core Application\n        MainViewModel --\u003e|Manages| SearchState\n        MainViewModel --\u003e|Hits| RegexCache[LRU Regex Cache]\n        MainViewModel --\u003e|Reads/Writes| SettingsService\n        MainViewModel --\u003e|Executes| PluginLoader\n    end\n\n    subgraph Data Sources\n        PluginLoader --\u003e| loads | IWindowProvider\n        IWindowProvider -.-\u003e WindowFinder\n        IWindowProvider -.-\u003e ChromeTabFinder\n        IWindowProvider -.-\u003e TerminalPlugin\n        IWindowProvider -.-\u003e NotepadPlusPlusPlugin\n        IWindowProvider -.-\u003e TeamsPlugin\n    end\n\n    MainViewModel --\u003e|Aggregates| WindowList[Filtered Window List]\n    WindowFinder --\u003e|Yields| WindowItem\n    ChromeTabFinder --\u003e|Yields| WindowItem\n    TerminalPlugin --\u003e|Yields| WindowItem\n    NotepadPlusPlusPlugin --\u003e|Yields| WindowItem\n    TeamsPlugin --\u003e|Yields| WindowItem\n    \n    style RegexCache fill:#f9f,stroke:#333,stroke-width:2px,color:black\n```\n\n## Performance\n\nSwitchBlade 1.5.1+ utilizes bleeding-edge .NET 9 features to ensure minimal resource footprint and maximum responsiveness.\n\n### Key Optimizations\n- **Zero-Allocation Window Scanning**: Uses `Span\u003cchar\u003e`, `stackalloc` and `Unsafe` pointers to retrieve window titles and binary paths without generating garbage (GC pressure).\n- **Source-Generated Interop**: Replaces slow `[DllImport]` with high-performance `[LibraryImport]` for all Windows API calls, ensuring trimming and AOT compatibility.\n- **Modern Async Polling**: Uses `PeriodicTimer` for lock-free, efficient background updates.\n- **Smart Caching**: Process names, paths, and icons come from a concurrent cache to minimize kernel transitions and I/O.\n- **Configurable Regex caching**: Implements an LRU (Least Recently Used) cache for compiled regex objects to ensure buttery-smooth search responsiveness during rapid typing.\n- **Immune to ReDoS**: Dynamically switches to the `.NET 9 NonBacktracking` engine for all user-provided patterns, providing guaranteed linear-time matching and protection against malicious regex hangs.\n- **ReadyToRun (R2R) Deployment**: Pre-compiled native code in the binary reduces startup time and eliminates JIT warm-up latency.\n\n```mermaid\ngraph TD\n    subgraph UI Thread\n        UI[Main Window]\n        RC[LRU Regex Cache]\n        NB[NonBacktracking Engine]\n    end\n\n    subgraph Background Service\n        BP[BackgroundPollingService]\n        PT[\"PeriodicTimer (Async)\"]\n    end\n\n    subgraph Core Logic\n        WF[WindowFinder]\n        NI[NativeInterop]\n    end\n\n    subgraph Windows OS\n        API1[EnumWindows]\n        API2[GetWindowTextW]\n    end\n\n    %% Discovery Path\n    UI -- Dispatcher --\u003e BP\n    BP -- Await Tick --\u003e PT\n    BP -- Refresh --\u003e WF\n    WF -- StackAlloc Buffer --\u003e NI\n    NI -- LibraryImport --\u003e API1\n    NI -- Unsafe Pointer --\u003e API2\n\n    %% Search Path\n    UI -- User Input --\u003e RC\n    RC -- Cache Hit/Miss --\u003e NB\n    NB -- Guaranteed O(n) --\u003e UI\n\n    style NI fill:#f9f,stroke:#333,stroke-width:2px,color:black\n    style WF fill:#bbf,stroke:#333,stroke-width:2px,color:black\n    style RC fill:#f9f,stroke:#333,stroke-width:2px,color:black\n    style NB fill:#bbf,stroke:#333,stroke-width:1px,color:black\n```\n\n## Fuzzy Search\n\nSwitchBlade 1.6.0 introduces intelligent fuzzy search that makes finding windows effortless.\n\n### Features\n\n| Feature | Description | Example |\n|:--------|:------------|:--------|\n| **Delimiter Equivalence** | Spaces, underscores, and dashes are treated identically | `hello there` matches `hello_there` |\n| **Subsequence Matching** | Characters must appear in order but not consecutively | `gc` matches `Google Chrome` |\n| **Case Insensitive** | Matching ignores letter case | `CHROME` matches `chrome` |\n| **Relevance Sorting** | Best matches appear first based on scoring | Exact matches rank highest |\n\n### Scoring System\n\nFuzzy search ranks results using a weighted scoring algorithm:\n\n| Bonus | Points | Awarded When |\n|:------|:------:|:-------------|\n| Base Match | +1 | Each matched character |\n| Contiguity | +2 | Consecutive character matches |\n| Word Boundary | +3 | Match at start of title |\n| Starts-With | +5 | Title begins with query |\n\n```mermaid\nflowchart TD\n    Start[User Types Query] --\u003e FastPath{Exact Substring?}\n    FastPath -- Yes --\u003e ExactScore[Calculate Exact Score + Bonuses]\n    FastPath -- No --\u003e Normalize\n    \n    subgraph Zero-Allocation Pipeline\n        Normalize[Normalize Title \u0026 Query]\n        Normalize --\u003e |stackalloc buffer| RemoveDelim[Remove Spaces/Underscores/Dashes]\n        RemoveDelim --\u003e |Span char| ToLower[Convert to Lowercase]\n    end\n    \n    ToLower --\u003e LengthCheck{Query \u003c= Title?}\n    LengthCheck -- No --\u003e NoMatch[Return 0]\n    LengthCheck -- Yes --\u003e Subsequence\n    \n    subgraph Subsequence Matching\n        Subsequence[Find Characters in Order]\n        Subsequence --\u003e |For each match| AddBase[+1 Base Score]\n        AddBase --\u003e Contiguous{Previous was adjacent?}\n        Contiguous -- Yes --\u003e AddCont[+2 Contiguity Bonus]\n        Contiguous -- No --\u003e CheckStart\n        AddCont --\u003e CheckStart{At position 0?}\n        CheckStart -- Yes --\u003e AddBoundary[+3 Word Boundary]\n        CheckStart -- No --\u003e NextChar[Continue]\n        AddBoundary --\u003e NextChar\n    end\n    \n    NextChar --\u003e AllFound{All chars found?}\n    AllFound -- No --\u003e NoMatch\n    AllFound -- Yes --\u003e StartsCheck{Started at pos 0?}\n    StartsCheck -- Yes --\u003e AddStarts[+5 Starts-With Bonus]\n    StartsCheck -- No --\u003e FinalScore\n    AddStarts --\u003e FinalScore[Return Total Score]\n    ExactScore --\u003e SortResults\n    FinalScore --\u003e SortResults[Sort by Score DESC]\n    \n    style Normalize fill:#f9f,stroke:#333,stroke-width:2px,color:black\n    style RemoveDelim fill:#f9f,stroke:#333,stroke-width:2px,color:black\n    style ToLower fill:#f9f,stroke:#333,stroke-width:2px,color:black\n```\n\n### Configuration\n\n- **Enable/Disable**: Toggle in Settings → Search \u0026 Performance → \"Enable Fuzzy Search\"\n- **Default**: Enabled\n- **Fallback**: When disabled, uses legacy regex/substring matching\n\n## Development\n\nFor information on how to build the project and create plugins, please refer to the following guides:\n\n- [Build Instructions](BUILD.md): Detailed steps for setting up your environment, building SwitchBlade, and running unit tests.\n- [Plugin Development Guide](PLUGIN_DEVELOPMENT.md): A comprehensive guide on building custom plugins for window discovery.\n\n### Unit Tests\nThe project includes comprehensive xUnit tests in `SwitchBlade.Tests/`. Run tests with:\n```powershell\ndotnet test SwitchBlade.Tests/SwitchBlade.Tests.csproj\n```\n\n### Plugin System\nSwitchBlade uses a contract-based plugin architecture.\n- **Interface**: `SwitchBlade.Contracts.IWindowProvider`\n- **Mechanism**: On startup, `PluginLoader` scans the `Plugins` directory for DLLs implementing `IWindowProvider`.\n- **Isolation**: Each plugin runs within the main application process but is logically isolated by the `WindowItem` source property.\n\n## Command-Line Arguments\n\nSwitchBlade supports the following command-line parameters (prefixes `/`, `--`, or `-` are all supported):\n\n| Parameter | Description |\n| :--- | :--- |\n| `/debug` | Enables verbose logging. Logs are saved to `%TEMP%\\switchblade_debug.log`. |\n| `/minimized` | Starts the application in the system tray without showing the main window. |\n| `/enablestartup` | Used by the installer to enable \"Launch on Startup\" in the Windows Registry on first run. |\n\n## Window Discovery Logic\n\nSwitchBlade uses a two-tier architecture for window discovery: fast in-process scanning for standard windows, and out-of-process UIA scanning for specialized plugins with **streaming results**.\n\n### Streaming NDJSON Protocol (v1.8.2+)\n\nUIA plugins run in parallel and emit results immediately as each completes. This eliminates the blocking behavior where fast plugins waited for slow ones.\n\n```mermaid\nsequenceDiagram\n    participant Main as SwitchBlade (Main)\n    participant Worker as UiaWorker.exe\n    participant Chrome as ChromeTabFinder\n    participant Terminal as WindowsTerminal\n\n    Main-\u003e\u003eWorker: Start process, send JSON request\n    \n    par Parallel Plugin Execution\n        Worker-\u003e\u003eChrome: GetWindows()\n        Worker-\u003e\u003eTerminal: GetWindows()\n    end\n    \n    Chrome--\u003e\u003eWorker: Results (15ms)\n    Worker--\u003e\u003eMain: {\"pluginName\":\"Chrome\",\"windows\":[...]}\n    Note over Main: UI updates immediately with Chrome tabs\n    \n    Terminal--\u003e\u003eWorker: Results (2000ms)\n    Worker--\u003e\u003eMain: {\"pluginName\":\"Terminal\",\"windows\":[...]}\n    Note over Main: Terminal tabs appear when ready\n    \n    Worker--\u003e\u003eMain: {\"isFinal\":true}\n    Worker-\u003e\u003eWorker: Exit (releases all COM objects)\n```\n\n### Architecture Overview\n\n```mermaid\nflowchart LR\n    Start[Start Scan] --\u003e Parallel{Parallel Execution}\n    Parallel --\u003e|Task 1| WF[\"WindowFinder (In-Process)\"]\n    Parallel --\u003e|Task 2| UIA[UiaWorkerClient]\n    \n    subgraph \"Main Process\"\n        WF --\u003e Enum[EnumWindows]\n        Enum --\u003e Filter{Is Visible?}\n        Filter -- Yes --\u003e Exclude{Handling Plugin Exists?}\n        Exclude -- No --\u003e Result1[Add WindowItem]\n    end\n\n    subgraph \"Child Process (SwitchBlade.UiaWorker.exe)\"\n        UIA --\u003e|JSON over Stdin| Plugins[Parallel Plugins]\n        Plugins --\u003e CTF[ChromeTabFinder]\n        Plugins --\u003e WTP[WindowsTerminalPlugin]\n        Plugins --\u003e NPP[NotepadPlusPlusPlugin]\n        \n        CTF --\u003e|Stream| NDJSON[NDJSON Output]\n        WTP --\u003e|Stream| NDJSON\n        NPP --\u003e|Stream| NDJSON\n    end\n    \n    Result1 --\u003e UI[Update UI]\n    NDJSON --\u003e|IAsyncEnumerable| UI\n```\n\n### Key Benefits\n\n| Aspect | Before (v1.8.1) | After (v1.8.2) |\n|--------|-----------------|----------------|\n| **Fast Plugin Visibility** | Blocked until all complete | Immediate |\n| **Plugin Execution** | Sequential | Parallel |\n| **Protocol** | Single JSON response | Streaming NDJSON |\n| **User Experience** | Delayed \"all at once\" | Progressive \"pop-in\" |\n\n### 1. Core Window Finder (`WindowFinder.cs`)\nThis is the built-in provider for standard desktop applications.\n- **Method**: Uses the Win32 `EnumWindows` API to iterate over all top-level windows on the desktop.\n- **Filtering**:\n  - Checks `IsWindowVisible`.\n  - Filters out known system noise (e.g., \"Program Manager\").\n  - **Zero-Allocation Process Lookup**: Uses specialized native APIs (`QueryFullProcessImageName`) instead of the heavy .NET `Process` class to identify window owners without allocating managed memory.\n  - **Smart De-Duplication**: It automatically inspects the `IBrowserSettingsProvider` list. If a window belongs to a process that is handled by a specialized plugin (e.g., \"chrome\", \"comet\"), `WindowFinder` **excludes** it. This prevents double-entries where both the generic window title and the specific tabs would appear.\n\n### 2. Chrome Tab Finder (`ChromeTabFinder.cs`)\nA specialized plugin for Chromium-based browsers (Chrome, Edge, Brave, Comet, etc.).\n- **Execution Mode**: Runs **Out-of-Process** via `SwitchBlade.UiaWorker.exe` to prevent native memory leaks.\n- **Discovery Strategy**:\n  1.  **Process Identification**: Identifies target processes by name (configurable).\n  2.  **Window Enumeration**: Uses `EnumWindows` (Win32) to find **ALL** top-level windows belonging to those PIDs.\n  3.  **UI Automation**: Attaches to each window using `System.Windows.Automation`.\n  4.  **Tree Traversal**: Performs a Breadth-First Search (BFS) of the automation tree to find elements with `ControlType.TabItem`.\n\n#### Thread Safety \u0026 Isolation\nSince this plugin runs in a separate process, it is immune to the \"FindAll\" memory leak inherent in Windows 11's UIA framework. When the worker process exits after scanning, all accumulated COM references are instantly released by the OS.\n\n### 3. Windows Terminal Plugin (`WindowsTerminalPlugin.cs`)\nA specialized plugin for Microsoft's Windows Terminal.\n- **Execution Mode**: Runs **Out-of-Process** via `SwitchBlade.UiaWorker.exe`.\n- **Discovery Strategy**:\n  1.  **Process Identification**: Identifies target processes by name (default: \"WindowsTerminal\", configurable via settings).\n  2.  **UI Automation**: Attaches to the main window handle (`MainWindowHandle`) of each identified process.\n  3.  **Tree Traversal (`ScanForTabs`)**: Performs a Breadth-First Search (BFS) of the automation tree up to a depth of 12 to find `ControlType.TabItem` elements.\n- **Fallback Mechanism**: If no tabs are discovered (often due to elevation/UIPI restrictions when SwitchBlade is not elevated), the plugin returns the main terminal window as a single searchable item.\n- **Activation**:\n  1.  Brings the main window to the foreground.\n  2.  Uses UI Automation patterns (`SelectionItemPattern` or `InvokePattern`) to programmatically select the specific tab requested by the user.\n\n### 4. Notepad++ Plugin (`NotepadPlusPlusPlugin.cs`)\nIndexes and switches between individual open files/tabs in Notepad++.\n- **Execution Mode**: Runs **Out-of-Process** via `SwitchBlade.UiaWorker.exe`.\n- **Mechanism**: Similar to the Terminal plugin, it uses UI Automation to traverse the document tabs in Notepad++.\n- **Strategy**: Identifies `notepad++` processes and scans for tab items to allow direct file-level switching.\n\n### 5. Microsoft Teams Plugin (`TeamsPlugin.cs`)\nA specialized plugin for Microsoft Teams (v2/New Teams).\n- **Execution Mode**: Runs **Out-of-Process** via `SwitchBlade.UiaWorker.exe` to isolate the main application from WebView2 memory characteristics.\n- **Discovery Strategy**:\n  1. **Process Identification**: Identifies `ms-teams` processes.\n  2. **Chat Parsing**: Scans the UI tree for `TreeItem` elements representing chats. Parses contact names and statuses (e.g., \"Available\", \"Busy\") using regex patterns derived from extensive testing.\n  3. **Chat Types**: Distinguishes between Individual, Group, and Meeting chats.\n- **Activation**:\n  - Uses a robust \"Pattern Cascade\" to activate chats:\n    1. `InvokePattern` (Click)\n    2. `SelectionItemPattern` (Select)\n    3. `ExpandCollapsePattern` (Expand)\n    4. `SetFocus` (Fallback)\n  - This ensures reliable switching regardless of the specific UI state of the chat item.\n\n## Async \u0026 Threading Model\n\n### Parallel Execution\nSwitchBlade does NOT block the UI thread while searching.\n- When `RefreshWindows()` is called, the application spawns a separate `Task` for each loaded `IWindowProvider`.\n- These tasks run in parallel on the ThreadPool. The fast `WindowFinder` typically finishes in \u003c10ms, while `ChromeTabFinder` may take 100-300ms depending on open tabs.\n\n### UI Marshalling\n- As each background task completes, it marshals its results back to the UI thread using `Application.Current.Dispatcher.Invoke`.\n- This creates a \"Pop-in\" effect where core windows appear instantly, followed shortly by browser tabs.\n\n## Smart Refresh \u0026 List Merge Strategy\n\nSwitchBlade uses a sophisticated incremental update strategy to keep the window list stable and prevent visual disruption during updates. The goal is to never clear the list and re-add all items, which would cause flickering and loss of user context.\n\n### Persistence Strategy\n1. **No Clear-On-Toggle**: When the Global Hotkey is pressed, the list is **NOT** cleared. The user immediately sees the results from the *previous* session while background scans run.\n2. **Provider-Isolated Updates**: Each window provider (e.g., `WindowFinder`, `ChromeTabFinder`) updates its own slice of the list independently. Changes from one provider don't affect items from other providers.\n\n### Incremental Merge Algorithm (O(N) Optimized)\n\nSwitchBlade uses a high-performance, two-phase synchronization algorithm to update the UI collection without full list refreshes. This ensures selection persistence and buttery-smooth animations.\n\n```mermaid\nflowchart TD\n    Start[\"Source List Received\"] --\u003e Phase1[\"Phase 1: Cleanup\"]\n    Phase1 --\u003e BuildSet[\"Build HashSet of Source Items\"]\n    BuildSet --\u003e ReverseLoop[\"Loop Collection Backwards\"]\n    ReverseLoop --\u003e Exists{\"In SourceSet?\"}\n    Exists -- No --\u003e Remove[\"RemoveAt i\"]\n    Exists -- Yes --\u003e NextDel[\"Next Item\"]\n    \n    Remove --\u003e NextDel\n    NextDel --\u003e|Done| Phase2[\"Phase 2: O(N) Two-Pointer Sync\"]\n    \n    Phase2 --\u003e InitPtr[\"Set ptr = 0\"]\n    InitPtr --\u003e SourceLoop[\"Loop through Source\"]\n    SourceLoop --\u003e Match{\"collection[ptr] == source[i]?\"}\n    \n    Match -- Yes --\u003e IncPtr[\"ptr++\"]\n    IncPtr --\u003e SourceLoop\n    \n    Match -- No --\u003e Find[\"Search Forward for Item\"]\n    Find --\u003e Found{\"Found?\"}\n    \n    Found -- Yes --\u003e Move[\"collection.Move foundAt -\u003e ptr\"]\n    Move --\u003e IncPtr\n    \n    Found -- No --\u003e Insert[\"collection.Insert ptr, item\"]\n    Insert --\u003e IncPtr\n    \n    SourceLoop --\u003e|Complete| End[\"Sync Finished\"]\n    \n    style Phase2 fill:#f9f,stroke:#333,stroke-width:2px,color:black\n    style Match fill:#bbf,stroke:#333,stroke-width:2px,color:black\n```\n\n#### Phase 1: Reconciliation (Cleanup)\nIdentifies and removes items that are no longer part of the current search results. Using a `HashSet\u003cWindowItem\u003e` ensures existence checks are $O(1)$.\n\n#### Phase 2: Two-Pointer Sync (Order \u0026 Stability)\nSynchronizes the collection order with the source list using a single pass ($O(N)$). It minimizes UI thread workload by only issuing `Move` or `Insert` commands when structural changes are detected. By searching forward from the current pointer, it avoids the $O(N^2)$ penalty of multiple `IndexOf` calls.\n\n### Selection Preservation\n\nDuring list updates, the selection behavior is controlled by the **List Refresh Behavior** setting:\n\n| Setting | Behavior |\n| :--- | :--- |\n| **Preserve scroll position** (default) | Selection is updated silently. The scroll position stays exactly where it was. The view does NOT auto-scroll to the selected item. |\n| **Follow selected window (Identity)** | Selection follows the same **window identity** (Hwnd + Title). If your selected window moves, the list auto-scrolls to keep it visible. |\n| **Keep selection index (Position)** | Selection stays at the current **index position**. If you're viewing item #3, you'll still be viewing item #3 after refresh (even if the window at that position changed). The list auto-scrolls to the new selection. |\n\n### Diff Key Design\n\nChrome tabs share the same `Hwnd` (the browser window handle), so we use a composite key:\n```\nIdentity = (Hwnd, Title)\n```\nThis allows us to:\n- Distinguish between tabs in the same browser window\n- Detect when a tab's title has changed (e.g., page navigation)\n- Properly track selection across refreshes\n\n### Thread Safety\n\nThe merge operation runs on background threads via `Task.Run()`, but all mutations to `_allWindows` and `FilteredWindows` are marshalled to the UI thread via `Dispatcher.Invoke()`. This ensures:\n- No race conditions on the ObservableCollection\n- WPF bindings receive proper change notifications\n- The UI remains responsive during long scans\n\n## Run as Administrator\n\nSome plugins require elevated privileges to fully inspect certain windows (e.g., tabs in an elevated Terminal or other admin-level applications).\n\n### Configuration\n- **Toggle**: Found in Settings → \"Run as Administrator\"\n- **Default**: Off (disabled)\n- **Effect**: When enabled, SwitchBlade displays a UAC prompt on startup\n\n### Behavior\nWhen the setting is toggled:\n1. The setting is saved immediately\n2. A dialog prompts the user to restart\n3. On next startup, SwitchBlade requests elevation via UAC\n\n\u003e **Note**: If \"Launch on Windows Startup\" is also enabled, and the user wants automatic elevation, they may need to configure a Scheduled Task with \"Run with highest privileges\" instead of the standard Run registry entry.\n\n## Background Polling\n\nSwitchBlade supports optional background polling to keep the window list up-to-date even when the application is not in focus.\n\n### Configuration\n- **Enable Background Polling**: Toggle in Settings (default: enabled).\n- **Polling Interval**: Configurable in Settings (default: 30 seconds, range: 5-120 seconds).\n\n### Concurrency Protection\nThe `BackgroundPollingService` uses a `SemaphoreSlim(1, 1)` to ensure only one refresh operation runs at a time. If a refresh is already in progress when the timer ticks, that tick is skipped. This prevents thread contention and race conditions on the window list.\n\n## Number Shortcuts\n\nSwitchBlade supports number shortcuts for instant window switching. When enabled, holding the modifier key and pressing a number key (1-9 or 0) will immediately activate the corresponding window in the list.\n\n### Key Mapping\n| Keys | Window Position |\n| :---: | :---: |\n| `Alt+1` | 1st window |\n| `Alt+2` | 2nd window |\n| ... | ... |\n| `Alt+9` | 9th window |\n| `Alt+0` | 10th window |\n\n### Configuration\n- **Enable Number Shortcuts**: Toggle in Settings (default: enabled)\n- **Shortcut Modifier**: Choose Alt, Ctrl, Shift, or None (default: Alt)\n- Supports both main keyboard number row and NumPad keys\n- When enabled, number badges appear next to the first 10 windows in the list\n\n### Smooth Reordering\nThe window list maintains a stable sort (by Process Name → Title → Handle) to minimize visual disruption when new windows appear. Combined with the incremental merge strategy, the numbered positions update smoothly without full list refreshes.\n\n### Badge Animation System\n\nThe Alt+Number badges feature a staggered animation that provides visual polish when the window list appears. Each badge fades in and slides from left to right in sequence.\n\n```mermaid\nsequenceDiagram\n    participant UI as UI Thread\n    participant BAS as BadgeAnimationService\n    participant B1 as Badge Alt+1\n    participant B2 as Badge Alt+2\n    participant B0 as Badge Alt+0\n\n    UI-\u003e\u003eBAS: TriggerStaggeredAnimationAsync()\n    BAS-\u003e\u003eBAS: ResetAnimationState()\n    \n    Note over BAS: Stagger delay = 75ms per badge\n    \n    BAS-\u003e\u003eB1: Start animation (0ms delay)\n    Note over B1: Opacity: 0→1\u003cbr/\u003eTranslateX: -20px→0\n    \n    BAS-\u003e\u003eB2: Start animation (75ms delay)\n    Note over B2: Opacity: 0→1\u003cbr/\u003eTranslateX: -20px→0\n    \n    BAS-\u003e\u003eB0: Start animation (675ms delay)\n    Note over B0: Last badge (index 9)\n    \n    Note over B1,B0: Each animation: 150ms duration, cubic ease-out\n```\n\n#### Animation Timing\n| Parameter | Value | Purpose |\n|:---|:---|:---|\n| **Stagger Delay** | 75ms | Time between each badge starting its animation |\n| **Duration** | 150ms | Total animation time per badge |\n| **Offset** | -20px | Starting X position (slides right to 0) |\n| **Easing** | Cubic ease-out | Smooth deceleration |\n\n#### HWND Tracking\nThe `BadgeAnimationService` tracks which window handles (HWNDs) have been animated to prevent re-animation:\n- When a window's title changes but HWND remains the same → badge stays visible (no re-animation)\n- When search text changes → animation state resets, badges re-animate with filtered results\n- When window hides and shows again → full reset, all badges animate fresh\n\n#### Configuration\n- **Enable Badge Animations**: Toggle in Settings (default: enabled)\n- When disabled, badges appear instantly at full opacity\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesericwong%2Fswitchblade","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamesericwong%2Fswitchblade","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesericwong%2Fswitchblade/lists"}