diff --git a/AmiReel.WinUI/.github/instructions/accessibility.instructions.md b/AmiReel.WinUI/.github/instructions/accessibility.instructions.md
new file mode 100644
index 0000000..e54c035
--- /dev/null
+++ b/AmiReel.WinUI/.github/instructions/accessibility.instructions.md
@@ -0,0 +1,62 @@
+---
+description: 'Accessibility requirements for interactive controls, keyboard navigation, screen readers, and contrast'
+applyTo: '**/*.cs, **/*.xaml'
+---
+
+# Accessibility
+
+These rules apply to **every UI change**. They are not optional add-ons.
+
+---
+
+## Rules
+
+- **Every interactive control** must have an `AutomationProperties.Name` or `AutomationProperties.LabeledBy`.
+- Add a stable, unique `AutomationProperties.AutomationId` for controls targeted by UI automation tests (and for key interactive elements).
+- Use semantic XAML controls — prefer `Button`, `HyperlinkButton`, `ListView` over styled `Border`/`Grid` with click handlers.
+- Ensure **keyboard navigation** works for all features:
+ - Logical tab order via `TabIndex`.
+ - `AccessKey` bindings for frequently used actions.
+ - `KeyboardAccelerator` for shortcut keys.
+- Maintain **minimum contrast ratios** (4.5:1 for normal text, 3:1 for large text) — test in High Contrast mode.
+- Support **screen readers** (Narrator / NVDA): test that all content is announced correctly.
+- Images must have `AutomationProperties.Name` describing the image purpose (or `AutomationProperties.AccessibilityView="Raw"` for decorative images).
+- Do not rely on colour alone to convey meaning — add icons, text, or patterns.
+
+## Anti-patterns
+
+- Clickable `TextBlock` or `Image` without `AutomationProperties`.
+- Custom controls that are not keyboard-focusable.
+- Using `Visibility.Collapsed` to "hide" content from screen readers (use `AccessibilityView` instead).
+
+## Validation
+
+- Build & register the MSIX package — see **Build, Run & Deploy** in `.github/agents/Agents.md`.
+- Test keyboard navigation: tab through every new/changed UI area.
+- Test High Contrast: switch to Windows High Contrast theme and verify readability.
+- Run Accessibility Insights for Windows on the app.
+
+### Verification Checklist
+
+- [ ] All interactive controls have `AutomationProperties.Name`
+- [ ] Keyboard navigation works for the changed area
+- [ ] Tested with High Contrast theme enabled
+- [ ] Tab through the entire UI with keyboard only.
+- [ ] Verify key interactive controls have stable, unique `AutomationProperties.AutomationId` values (especially controls used by UI automation tests).
+- [ ] Switch to Windows High Contrast theme and verify readability.
+- [ ] Run Narrator and verify all controls are announced correctly.
+- [ ] Run **Accessibility Insights for Windows** on the app.
+
+## Must Read & Research
+
+> **Agent Rule:** Before any accessibility-related change, you **must** fetch and review these references using `fetch_webpage`. Apply what you learn.
+
+| # | Reference | When to consult |
+|---|---|---|
+| 1 | [Accessibility in WinUI](https://learn.microsoft.com/en-us/windows/apps/design/accessibility/accessibility) | Any UI change — verify accessibility approach |
+| 2 | [AutomationProperties](https://learn.microsoft.com/en-us/windows/apps/design/accessibility/basic-accessibility-information) | Adding or modifying interactive controls |
+| 3 | [Accessibility Insights](https://accessibilityinsights.io/docs/windows/overview/) | Testing tool — run before finalizing UI changes |
+| 4 | [Keyboard Accessibility](https://learn.microsoft.com/en-us/windows/apps/design/accessibility/keyboard-accessibility) | Adding navigation, focus management, or shortcut keys |
+| 5 | [High Contrast Themes](https://learn.microsoft.com/en-us/windows/apps/design/accessibility/high-contrast-themes) | Adding custom styles, colours, or theme resources |
+
+
diff --git a/AmiReel.WinUI/.github/instructions/code-quality.instructions.md b/AmiReel.WinUI/.github/instructions/code-quality.instructions.md
new file mode 100644
index 0000000..d18b4b8
--- /dev/null
+++ b/AmiReel.WinUI/.github/instructions/code-quality.instructions.md
@@ -0,0 +1,170 @@
+---
+description: 'Static analysis, StyleCop, EditorConfig, naming conventions, and code cleanup rules'
+applyTo: '**/*.cs, **/*.editorconfig, **/stylecop.json'
+---
+
+# Code Quality — Static Analysis, StyleCop & Code Cleanup
+
+Maintain strict code quality through automated analysis and consistent style enforcement.
+
+---
+
+## 1. Static Analysis (Roslyn Analyzers)
+
+### Required Analyzer Packages
+
+Add these to the `.csproj` if not already present:
+
+```xml
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+```
+
+### Analysis Configuration in `.csproj`
+
+```xml
+
+ true
+ latest-recommended
+ true
+ false
+ enable
+
+```
+
+### Rule Enforcement
+
+Follow **all** CA* (quality) and IDE* (code style) analyzer rules at their configured severity. Do not cherry-pick — obey every warning the analyzers report. When encountering a specific rule violation, fetch the corresponding documentation from the **Must Read & Research** references below to understand and apply the correct fix.
+
+### `.editorconfig`
+
+The project's `.editorconfig` in the solution root is the source of truth for code style. Obey all rules defined there. When creating or modifying `.editorconfig`, fetch the [EditorConfig Reference](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/code-style-rule-options) for the full list of available settings.
+
+Key project conventions enforced via `.editorconfig`:
+- Private fields use `_camelCase` prefix (SA1101 suppressed, SA1309 suppressed).
+- File-scoped namespaces are required.
+- `this.` qualification is not used.
+
+---
+
+## 2. StyleCop Rules
+
+### StyleCop Configuration (`stylecop.json`)
+
+Place this file in the project root alongside the `.csproj`:
+
+```json
+{
+ "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
+ "settings": {
+ "documentationRules": {
+ "companyName": "YourProjectName",
+ "copyrightText": "Copyright (c) {companyName}. All rights reserved.",
+ "xmlHeader": false,
+ "documentInterfaces": true,
+ "documentExposedElements": true,
+ "documentInternalElements": false,
+ "documentPrivateElements": false,
+ "documentPrivateFields": false
+ },
+ "orderingRules": {
+ "usingDirectivesPlacement": "outsideNamespace",
+ "systemUsingDirectivesFirst": true
+ },
+ "layoutRules": {
+ "newlineAtEndOfFile": "require"
+ },
+ "namingRules": {
+ "allowCommonHungarianPrefixes": false
+ }
+ }
+}
+```
+
+### Rule Enforcement
+
+Follow **all** SA* (StyleCop) rules at their configured severity. When encountering a specific SA* violation, fetch the [StyleCop Rules Reference](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/DOCUMENTATION.md) to understand the rule and apply the correct fix. Do not suppress rules without justification in a code comment.
+
+---
+
+## 3. Code Cleanup Rules (Always Enforced)
+
+### After Every Edit
+
+1. **Remove unused `using` statements** — No unused imports should remain.
+2. **Remove commented-out code** — Version control tracks history; dead code is noise.
+3. **Remove unused variables and fields** — If it's declared but never read, delete it.
+4. **Remove empty methods** — If an event handler or override does nothing, remove it.
+5. **Simplify code** — Apply IDE suggestions (IDE0001–IDE0090) for:
+ - Removing unnecessary casts
+ - Simplifying `default` expressions
+ - Using pattern matching
+ - Using null-coalescing operators
+
+### Naming Conventions
+
+| Element | Convention | Example |
+|---|---|---|
+| Class / Struct | PascalCase | `MainViewModel` |
+| Interface | I + PascalCase | `INavigationService` |
+| Public method | PascalCase | `LoadDataAsync()` |
+| Private method | PascalCase | `ValidateInput()` |
+| Public property | PascalCase | `CurrentPage` |
+| Private field | _camelCase | `_settingsService` |
+| Parameter | camelCase | `userName` |
+| Local variable | camelCase | `itemCount` |
+| Constant | PascalCase | `MaxRetryCount` |
+| Async method | Suffix `Async` | `FetchDataAsync()` |
+| Boolean | Prefix `Is/Has/Can` | `IsLoading`, `HasAccess` |
+
+### File Organization
+
+Each `.cs` file should follow this order:
+
+1. `using` directives (System first, then others, alphabetically)
+2. Namespace declaration (file-scoped)
+3. Class/struct/interface declaration
+4. Inside the type:
+ 1. Constants
+ 2. Static fields
+ 3. Instance fields
+ 4. Constructors
+ 5. Properties
+ 6. Public methods
+ 7. Private/internal methods
+ 8. Event handlers
+ 9. Nested types
+
+---
+
+## Validation
+
+- Build & register the MSIX package — see **Build, Run & Deploy** in `.github/agents/Agents.md`.
+- Fix **all** warnings — do not suppress without justification in a code comment.
+- Verify no unused `using` statements remain after every edit.
+- Verify no commented-out code remains.
+- Confirm naming conventions match the table above for every new symbol.
+
+---
+
+## Must Read & Research
+
+> **Agent Rule:** Before configuring analyzers, fixing warnings, or adjusting code style, you **must** fetch and review the relevant references below using `fetch_webpage`. Apply what you learn — do not skip this step.
+
+| # | Reference | When to consult |
+|---|---|---|
+| 1 | [.NET Code Analysis Overview](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/overview) | Setting up or modifying analyzer configuration |
+| 2 | [Code Style Rules (IDE0001–IDE0090)](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/) | Resolving IDE* warnings or adjusting `.editorconfig` |
+| 3 | [Quality Rules (CA*)](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/) | Resolving CA* warnings or suppressing with justification |
+| 4 | [StyleCop.Analyzers GitHub](https://github.com/DotNetAnalyzers/StyleCopAnalyzers) | Adding/updating StyleCop package or configuration |
+| 5 | [StyleCop Rules Reference](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/DOCUMENTATION.md) | Understanding specific SA* rule violations |
+| 6 | [EditorConfig Reference](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/code-style-rule-options) | Modifying `.editorconfig` style or severity settings |
+| 7 | [.NET Naming Conventions](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/identifier-names) | Verifying naming patterns for types, members, parameters |
+
+
diff --git a/AmiReel.WinUI/.github/instructions/design-principles.instructions.md b/AmiReel.WinUI/.github/instructions/design-principles.instructions.md
new file mode 100644
index 0000000..296f716
--- /dev/null
+++ b/AmiReel.WinUI/.github/instructions/design-principles.instructions.md
@@ -0,0 +1,134 @@
+---
+description: 'Design principles (DRY, KISS, SOLID, YAGNI) enforced in every code change'
+applyTo: '**/*.cs, **/*.xaml'
+---
+
+# Design Principles
+
+Apply these principles in **every change** you make to this codebase. When in doubt, favour simplicity and clarity over cleverness.
+
+---
+
+## 1. DRY — Don't Repeat Yourself
+
+> *"Every piece of knowledge must have a single, unambiguous, authoritative representation within a system."*
+
+### Rules
+- Before writing new code, **search** the codebase for existing implementations that solve the same problem.
+- Extract shared logic into helper methods, base classes, or services.
+- If you find duplicated code during a task, **refactor it** as part of the same change.
+- Prefer generic/reusable components over copy-paste variations.
+
+### Anti-patterns to avoid
+- Copy-pasting code between classes/files instead of extracting a shared method.
+- Creating multiple converters/helpers that do the same thing.
+- Duplicating validation logic across ViewModel and Model layers.
+
+---
+
+## 2. KISS — Keep It Simple, Stupid
+
+> *"Simplicity is the ultimate sophistication."*
+
+### Rules
+- Choose the **simplest approach** that meets the requirement.
+- Avoid unnecessary abstractions, inheritance hierarchies, or patterns that add complexity without clear benefit.
+- Write code that **reads like plain English** — favour descriptive names over comments.
+- If a method is longer than ~30 lines, consider splitting it.
+- If a class does more than one thing, split it (see SRP below).
+
+### Anti-patterns to avoid
+- Over-engineering with factories/builders/strategies for simple object creation.
+- Creating deep inheritance trees when composition would suffice.
+- Using complex LINQ chains when a simple `foreach` is clearer.
+
+---
+
+## 3. SOLID Principles
+
+### 3.1 SRP — Single Responsibility Principle
+> *"A class should have only one reason to change."*
+
+- Each class/file should have **one clear responsibility**.
+- ViewModels handle UI state & commands. Services handle business logic. Models hold data.
+- If a class name contains "And" or "Manager", it likely violates SRP.
+
+### 3.2 OCP — Open/Closed Principle
+> *"Software entities should be open for extension, but closed for modification."*
+
+- Use interfaces and abstract classes so behaviour can be extended without modifying existing code.
+- Prefer adding new implementations over modifying existing ones.
+- Use dependency injection to swap implementations.
+
+### 3.3 LSP — Liskov Substitution Principle
+> *"Objects of a superclass should be replaceable with objects of its subclasses without altering correctness."*
+
+- Derived classes must honour the contracts of their base classes.
+- Never throw `NotImplementedException` in overridden methods — if a subclass can't fulfil the contract, the inheritance is wrong.
+
+### 3.4 ISP — Interface Segregation Principle
+> *"No client should be forced to depend on methods it does not use."*
+
+- Keep interfaces small and focused.
+- Prefer multiple small interfaces over one large one.
+- Example: `INavigationService`, `IDialogService`, `ISettingsService` — not `IAppService`.
+
+### 3.5 DIP — Dependency Inversion Principle
+> *"Depend upon abstractions, not concretions."*
+
+- High-level modules must not depend on low-level modules. Both should depend on abstractions.
+- Use constructor injection for dependencies.
+- Register services in a DI container (e.g., `Microsoft.Extensions.DependencyInjection`).
+
+---
+
+## 4. YAGNI — You Aren't Gonna Need It
+
+> *"Don't add functionality until it is necessary."*
+
+### Rules
+- Only implement what is **explicitly requested** or **clearly needed right now**.
+- Do not add "just in case" parameters, methods, or abstractions.
+- If you're unsure whether something is needed, **leave it out** — it can always be added later.
+
+### Anti-patterns to avoid
+- Adding unused interface methods "for future use".
+- Building a generic framework when a single concrete class suffices.
+- Creating configuration options nobody has asked for.
+
+---
+
+## Quick Reference Checklist
+
+Before submitting any code change, verify:
+
+- [ ] No duplicated code exists (DRY)
+- [ ] The solution is as simple as possible (KISS)
+- [ ] Each class has one responsibility (SRP)
+- [ ] New behaviour is added via extension, not modification (OCP)
+- [ ] Derived types can substitute their base types (LSP)
+- [ ] Interfaces are small and focused (ISP)
+- [ ] Dependencies point toward abstractions (DIP)
+- [ ] No speculative features were added (YAGNI)
+
+## Validation
+
+- Review every new/changed class for SRP violations — ask "does this class have more than one reason to change?"
+- Search the codebase for duplicate logic before adding new helpers: `grep_search` for similar method names or patterns.
+- Verify no speculative code was added — every line must trace back to the original request.
+- Build & register the MSIX package — see **Build, Run & Deploy** in `.github/agents/Agents.md`.
+
+---
+
+## Must Read & Research
+
+> **Agent Rule:** Before making any code change related to design principles, you **must** fetch and review the relevant references below using `fetch_webpage`. Apply what you learn — do not skip this step.
+
+| # | Reference | When to consult |
+|---|---|---|
+| 1 | [SOLID Principles in C# — Microsoft Learn](https://learn.microsoft.com/en-us/archive/msdn-magazine/2014/may/csharp-best-practices-dangers-of-violating-solid-principles-in-csharp) | Adding/refactoring classes, interfaces, or inheritance |
+| 2 | [.NET Design Guidelines](https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/) | Designing public APIs, naming, type design |
+| 3 | [Framework Design Guidelines (Book)](https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/) | Deep-dive on member design, exception patterns, collections |
+| 4 | [Clean Code Summary](https://gist.github.com/wojteklu/73c6914cc446146b8b533c0988cf8d29) | Code readability, function size, naming clarity |
+
+
diff --git a/AmiReel.WinUI/.github/instructions/globalization.instructions.md b/AmiReel.WinUI/.github/instructions/globalization.instructions.md
new file mode 100644
index 0000000..6816b6d
--- /dev/null
+++ b/AmiReel.WinUI/.github/instructions/globalization.instructions.md
@@ -0,0 +1,55 @@
+---
+description: 'Globalization & Localization requirements for user-facing strings, resource files, and culture-aware formatting'
+applyTo: '**/*.cs, **/*.xaml, **/*.resw'
+---
+
+# Globalization & Localization
+
+These rules apply to **every feature and change** involving user-facing text. They are not optional add-ons.
+
+---
+
+## Rules
+
+- **All user-facing strings** (UI text, error messages, tooltips) must come from `.resw` resource files — never hard-code them in XAML or C#.
+- Resource file location: `Strings/en-us/Resources.resw` (default locale).
+- Use `x:Uid` in XAML to bind controls to resource keys:
+ ```xml
+
+ ```
+ With a matching `.resw` entry: `WelcomeMessage.Text` = "Welcome!"
+- In code-behind / ViewModels, use the `ResourceLoader`:
+ ```csharp
+ var loader = new Microsoft.Windows.ApplicationModel.Resources.ResourceLoader();
+ string message = loader.GetString("ErrorFileNotFound");
+ ```
+- **Format dates, numbers, and currencies** using `CultureInfo.CurrentCulture` or `DateTimeFormatter` — never assume a specific regional format.
+- Avoid concatenating translated strings — use format placeholders (`{0}`, `{1}`).
+- Design UI layouts to accommodate text expansion (~30-40% longer for German vs. English).
+
+## Anti-patterns
+
+- Hard-coded strings in `.xaml` or `.cs` files (e.g., `Content="Save"`).
+- Using `string.Format` with hard-coded ordinal assumptions.
+- Fixed-width UI elements that clip translated text.
+
+## Validation
+
+- Build & register the MSIX package — see **Build, Run & Deploy** in `.github/agents/Agents.md`.
+- Check for hard-coded strings: search `Content="` and `Text="` in `.xaml` files — replace with `x:Uid`.
+
+### Verification Checklist
+
+- [ ] All user-facing strings are in `.resw` resource files
+
+## Must Read & Research
+
+> **Agent Rule:** Before any localization-related change, you **must** fetch and review these references using `fetch_webpage`. Apply what you learn.
+
+| # | Reference | When to consult |
+|---|---|---|
+| 1 | [Globalize your WinUI app](https://learn.microsoft.com/en-us/windows/apps/design/globalizing/guidelines-and-checklist-for-globalizing-your-app) | Adding any new user-facing strings or culture-aware formatting |
+| 2 | [Resource Management System](https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/mrtcore/localize-strings) | Setting up or modifying `.resw` files and `ResourceLoader` usage |
+| 3 | [WinUI Localization with x:Uid](https://learn.microsoft.com/en-us/windows/apps/develop/ui-input/localizing-strings) | Binding XAML controls to localized resources via `x:Uid` |
+
+
diff --git a/AmiReel.WinUI/.github/instructions/performance.instructions.md b/AmiReel.WinUI/.github/instructions/performance.instructions.md
new file mode 100644
index 0000000..09eed79
--- /dev/null
+++ b/AmiReel.WinUI/.github/instructions/performance.instructions.md
@@ -0,0 +1,53 @@
+---
+description: 'Performance requirements for data binding, layout, threading, and collection virtualization'
+applyTo: '**/*.cs, **/*.xaml'
+---
+
+# Performance
+
+These rules apply to **every feature and change**. They are not optional add-ons.
+
+---
+
+## Rules
+
+- **Use `x:Bind`** (compiled bindings) instead of `{Binding}` — it's faster and type-safe.
+- Use **`x:Load`** (or `x:DeferLoadStrategy`) to defer loading of UI elements not immediately visible.
+- Avoid heavy work on the UI thread — use `Task.Run` for CPU-bound work and `async/await` for I/O.
+- Use **virtualizing panels** (`ItemsRepeater` with `StackLayout`, or `ListView`) for long lists — never use `StackPanel` with hundreds of items.
+- **Cache** expensive computations and HTTP responses when appropriate.
+- Minimize XAML visual tree depth — deep nesting hurts layout performance.
+- Use **incremental loading** (`ISupportIncrementalLoading`) for large data sets.
+- Profile with **Visual Studio Diagnostics Tools** and **PerfView** before and after optimizations.
+- Be cautious with `DispatcherQueue.TryEnqueue` — don't flood the dispatcher queue.
+
+## Anti-patterns
+
+- Blocking the UI thread with `.Result` or `.GetAwaiter().GetResult()`.
+- Loading all data upfront when only a subset is needed.
+- Creating new `HttpClient` instances per request (use `IHttpClientFactory`).
+- Using `FindName()` or `VisualTreeHelper` in tight loops.
+
+## Validation
+
+- Build & register the MSIX package — see **Build, Run & Deploy** in `.github/agents/Agents.md`.
+
+### Verification Checklist
+
+- [ ] No blocking calls on the UI thread
+- [ ] `x:Bind` is used instead of `{Binding}`
+- [ ] Large lists use virtualization
+
+## Must Read & Research
+
+> **Agent Rule:** Before any performance-sensitive change (data binding, layout, collections, async), you **must** fetch and review these references using `fetch_webpage`. Apply what you learn.
+
+| # | Reference | When to consult |
+|---|---|---|
+| 1 | [Performance best practices for WinUI 3](https://learn.microsoft.com/en-us/windows/apps/performance/) | Any change touching UI rendering, data loading, or threading |
+| 2 | [x:Bind markup extension](https://learn.microsoft.com/en-us/windows/uwp/xaml-platform/x-bind-markup-extension) | Adding or modifying XAML data bindings |
+| 3 | [x:Load attribute](https://learn.microsoft.com/en-us/windows/uwp/xaml-platform/x-load-attribute) | Deferring UI element loading |
+| 4 | [Optimize XAML layout](https://learn.microsoft.com/en-us/windows/apps/performance/optimize-xaml-layout) | Restructuring XAML panels, reducing visual tree depth |
+| 5 | [ListView optimization](https://learn.microsoft.com/en-us/windows/apps/performance/optimize-listview) | Working with lists, collections, or `ItemsRepeater` |
+
+
diff --git a/AmiReel.WinUI/.github/instructions/security.instructions.md b/AmiReel.WinUI/.github/instructions/security.instructions.md
new file mode 100644
index 0000000..6eeab88
--- /dev/null
+++ b/AmiReel.WinUI/.github/instructions/security.instructions.md
@@ -0,0 +1,55 @@
+---
+description: 'Security requirements for secrets management, input validation, permissions, and secure coding'
+applyTo: '**/*.cs, **/*.appxmanifest'
+---
+
+# Security
+
+These rules apply to **every feature and change**. They are not optional add-ons.
+
+---
+
+## Rules
+
+- **Never hard-code secrets** (API keys, passwords, connection strings) — use environment variables, Windows Credential Manager, or Azure Key Vault.
+- Validate and sanitize **all external input** (user input, file content, network responses).
+- Use `SecureString` or `PasswordVault` for sensitive data in memory when practical.
+- Follow the **principle of least privilege** — request only the permissions the app actually needs in `Package.appxmanifest`.
+- Keep NuGet packages up to date — run `dotnet list package --outdated` regularly.
+- Enable **code signing** for published MSIX packages. Use the `winapp` CLI rather than hand-rolling `signtool`:
+ - Generate a development certificate matching the manifest publisher: `winapp cert generate --manifest .\Package.appxmanifest --install`.
+ - Inspect a cert before signing: `winapp cert info .\devcert.pfx`.
+ - Sign an existing file: `winapp sign .\MyApp.msix --cert .\devcert.pfx`.
+ - Build + sign in one step: `winapp pack .\bin\\Release\\win- --cert .\devcert.pfx`.
+ - Production releases must be signed by a trusted certificate authority -- never ship the development cert.
+- When using `HttpClient`, always validate TLS certificates and use HTTPS.
+- Never log sensitive data (PII, tokens, passwords).
+
+## Anti-patterns
+
+- Storing secrets in `appsettings.json` committed to source control.
+- Disabling TLS validation for debugging and forgetting to re-enable it.
+- Using `Process.Start` with unsanitized user input.
+- Broad `try { } catch (Exception) { }` that swallows errors silently without any logging.
+
+## Validation
+
+- Build & register the MSIX package — see **Build, Run & Deploy** in `.github/agents/Agents.md`.
+- Check for hard-coded secrets: search for `password`, `apikey`, `secret`, `connectionstring` in `.cs` files.
+
+### Verification Checklist
+
+- [ ] No secrets are hard-coded
+
+## Must Read & Research
+
+> **Agent Rule:** Before any security-related change (auth, input handling, permissions, HTTP), you **must** fetch and review these references using `fetch_webpage`. Apply what you learn.
+
+| # | Reference | When to consult |
+|---|---|---|
+| 1 | [.NET Security Best Practices](https://learn.microsoft.com/en-us/dotnet/standard/security/) | Any code handling credentials, tokens, or sensitive data |
+| 2 | [Secure coding guidelines for .NET](https://learn.microsoft.com/en-us/dotnet/standard/security/secure-coding-guidelines) | Input validation, exception handling, type safety |
+| 3 | [MSIX Security](https://learn.microsoft.com/en-us/windows/msix/msix-container) | Packaging, signing, or distribution changes |
+| 4 | [Package.appxmanifest capabilities](https://learn.microsoft.com/en-us/windows/uwp/packaging/app-capability-declarations) | Adding or modifying app capabilities/permissions |
+
+
diff --git a/AmiReel.WinUI/.github/instructions/testing.instructions.md b/AmiReel.WinUI/.github/instructions/testing.instructions.md
new file mode 100644
index 0000000..8a08739
--- /dev/null
+++ b/AmiReel.WinUI/.github/instructions/testing.instructions.md
@@ -0,0 +1,284 @@
+---
+description: 'Unit testing standards, test project setup, naming, build & run commands'
+applyTo: '**/*Tests.cs, **/*Test.cs, **/*.Tests.csproj'
+---
+
+# Testing — Unit Tests, Build & Run
+
+Every public method and class must have corresponding unit tests. Tests are not optional.
+
+---
+
+## 1. Test Framework & Project Setup
+
+### Recommended Stack
+
+| Component | Package | Purpose |
+|---|---|---|
+| Test Framework | `MSTest` | Test runner & assertions |
+| Mocking | `Moq` | Mock dependencies |
+| UI Testing | `Microsoft.Windows.Apps.Test` | WinUI UI automation (optional) |
+
+### Test Project Setup
+
+Create a test project alongside the main project:
+
+```
+/
+ / ← Main app project
+ .Tests/ ← Unit test project
+```
+
+Test project `.csproj` should reference the main project:
+
+```xml
+
+
+
+
+ true
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+---
+
+## 2. Test Writing Rules
+
+### What to Test
+- **All public methods** in ViewModels, Services, Helpers, and Models.
+- **Edge cases** — null inputs, empty collections, boundary values.
+- **Error paths** — exception handling, invalid state transitions.
+- **Business logic** — calculations, transformations, state management.
+
+### What NOT to Test (Directly)
+- XAML layout / visual rendering (use UI tests for that).
+- Framework internals (e.g., `InitializeComponent()`).
+- Private methods — test them indirectly through public methods.
+
+### Test Naming Convention
+
+Use the pattern: `MethodName_Scenario_ExpectedResult`
+
+```csharp
+[TestMethod]
+public void CalculateTotal_WithEmptyCart_ReturnsZero() { }
+
+[TestMethod]
+public void LoadDataAsync_WhenServiceThrows_SetsErrorState() { }
+
+[TestMethod]
+public async Task SaveAsync_WithValidInput_ReturnsTrue() { }
+```
+
+### Test Structure (AAA Pattern)
+
+Every test follows **Arrange → Act → Assert**:
+
+```csharp
+[TestMethod]
+public void Add_TwoPositiveNumbers_ReturnsSum()
+{
+ // Arrange
+ var calculator = new Calculator();
+
+ // Act
+ int result = calculator.Add(2, 3);
+
+ // Assert
+ Assert.AreEqual(5, result);
+}
+```
+
+### ViewModel Testing Example
+
+```csharp
+[TestMethod]
+public async Task LoadItemsAsync_OnSuccess_PopulatesItems()
+{
+ // Arrange
+ var mockService = new Mock();
+ mockService
+ .Setup(s => s.GetItemsAsync())
+ .ReturnsAsync(new List- { new("Test") });
+
+ var viewModel = new MainViewModel(mockService.Object);
+
+ // Act
+ await viewModel.LoadItemsAsync();
+
+ // Assert
+ Assert.AreEqual(1, viewModel.Items.Count);
+ Assert.IsFalse(viewModel.IsLoading);
+}
+```
+
+---
+
+## 3. Test Organization
+
+### File Structure
+
+Mirror the main project's folder structure (defined in [winui-best-practices](winui-best-practices.instructions.md)) in the test project. As the main project grows with subfolders under `ViewModels/`, `Services/`, `Views/`, etc., the test project must grow organically in the same way. This alignment enables on-demand test runs scoped to the area you changed:
+
+```
+/ .Tests/
+ Models/ Models/
+ User.cs UserTests.cs
+ ViewModels/ ViewModels/
+ MainViewModelTests.cs MainViewModelTests.cs
+ Settings/ Settings/
+ ThemeViewModel.cs ThemeViewModelTests.cs
+ Services/ Services/
+ DataService.cs DataServiceTests.cs
+ Auth/ Auth/
+ AuthService.cs AuthServiceTests.cs
+ Helpers/ Helpers/
+ StringHelper.cs StringHelperTests.cs
+ Converters/ Converters/
+ BoolToVisibilityConverter.cs BoolToVisibilityConverterTests.cs
+```
+
+### One Test Class per Class Under Test
+
+```csharp
+namespace .Tests.ViewModels;
+
+[TestClass]
+public class MainViewModelTests
+{
+ // All tests for MainViewModel go here
+}
+```
+
+### Running Tests On-Demand
+
+After a change, run only the tests related to the affected area instead of the full suite. Detect the platform first (matching the convention in `.github/agents/Agents.md`):
+
+```powershell
+# Run from the test project folder
+cd .Tests
+
+# Detect platform once per session (AMD64 -> x64; ARM64/x86 unchanged)
+$arch = $env:PROCESSOR_ARCHITECTURE
+$Platform = if ($arch -eq 'AMD64') { 'x64' } else { $arch }
+
+# Run tests for a specific class
+dotnet test -c Debug -p:Platform=$Platform --filter "FullyQualifiedName~MainViewModelTests"
+
+# Run a single test
+dotnet test -c Debug -p:Platform=$Platform --filter "FullyQualifiedName~MainViewModelTests.LoadItemsAsync_OnSuccess_PopulatesItems"
+
+# Run all tests in a namespace (e.g., all ViewModel tests)
+dotnet test -c Debug -p:Platform=$Platform --filter "FullyQualifiedName~Tests.ViewModels"
+
+# Run tests in a subfolder namespace (e.g., only Settings ViewModels)
+dotnet test -c Debug -p:Platform=$Platform --filter "FullyQualifiedName~Tests.ViewModels.Settings"
+
+# Run the full suite (for cross-cutting changes)
+dotnet test -c Debug -p:Platform=$Platform
+```
+```
+
+---
+
+## 4. Test-Specific Commands
+
+For general build and register commands, see **Build, Run & Deploy** in `.github/agents/Agents.md`.
+For on-demand test filtering, see **Running Tests On-Demand** above.
+
+Below are additional test commands:
+
+### Build Only the Test Project
+
+```powershell
+cd .Tests
+$arch = $env:PROCESSOR_ARCHITECTURE
+$Platform = if ($arch -eq 'AMD64') { 'x64' } else { $arch }
+dotnet build -c Debug -p:Platform=$Platform
+```
+
+### Run Tests with Verbose Output
+
+```powershell
+dotnet test -c Debug -p:Platform=$Platform --verbosity normal
+```
+
+---
+
+## 5. Agent Workflow for Tests
+
+When you write or modify code, follow this sequence:
+
+1. **Implement the feature or fix** in the main project.
+2. **Write unit tests** for every new/changed public method.
+3. **Build** — see **Build, Run & Deploy** in `.github/agents/Agents.md`. Fix all errors and warnings.
+4. **Run tests** — `dotnet test -c Debug -p:Platform=$Platform` (from the test project folder; detect `$Platform` as shown above) and ensure all pass.
+5. **Review** — Confirm tests cover the happy path, edge cases, and error cases.
+
+### When Modifying Existing Code
+
+1. **Run existing tests first** to establish a baseline.
+2. Make the code change.
+3. **Run tests again** — fix any failures.
+4. **Add new tests** if the change introduces new behaviour.
+
+### Coverage Goals
+
+- Aim for **80%+ code coverage** on business logic (ViewModels, Services).
+- 100% coverage of utility/helper methods.
+- UI code-behind is exempt from unit test coverage (tested via integration/UI tests).
+
+---
+
+## 6. Common Test Pitfalls
+
+| Pitfall | Fix |
+|---|---|
+| Test depends on another test's state | Each test must be fully independent |
+| Testing multiple things in one test | One assertion per logical concept |
+| Tests pass but don't actually verify anything | Always have meaningful assertions |
+| Mocking too much | Mock only external dependencies, not the class under test |
+| Testing implementation details | Test behaviour and outcomes, not internal method calls |
+| Async tests without `await` | Always `await` async methods and use `async Task` return type |
+
+---
+
+## Validation
+
+- Build & run tests — see **Build, Run & Deploy** in `.github/agents/Agents.md`.
+- Verify all tests pass — zero failures, zero skipped without justification.
+- Verify naming follows `MethodName_Scenario_ExpectedResult` pattern.
+- Verify AAA structure (Arrange/Act/Assert) in every test method.
+- Confirm coverage goals: 80%+ on ViewModels/Services, 100% on helpers.
+
+---
+
+## Must Read & Research
+
+> **Agent Rule:** Before writing or modifying tests, you **must** fetch and review the relevant references below using `fetch_webpage`. Apply what you learn — do not skip this step.
+
+| # | Reference | When to consult |
+|---|---|---|
+| 1 | [Unit testing C# with MSTest](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-with-mstest) | Setting up test project, writing first tests, MSTest attributes |
+| 2 | [Unit testing best practices .NET](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices) | Every time you write tests — naming, structure, AAA pattern |
+| 3 | [Moq Quickstart](https://github.com/devlooped/moq/wiki/Quickstart) | Mocking interfaces, setting up `Returns`/`Throws`, verifying calls |
+| 4 | [FluentAssertions Documentation](https://fluentassertions.com/introduction) | Writing expressive assertions (`Should().Be()`, collections, exceptions) |
+| 5 | [dotnet test CLI](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-test) | Running tests from terminal, filtering, verbosity options |
+| 6 | [Test Explorer in Visual Studio](https://learn.microsoft.com/en-us/visualstudio/test/run-unit-tests-with-test-explorer) | Debugging tests, viewing coverage, understanding test output |
+
+
diff --git a/AmiReel.WinUI/.github/instructions/windows-apis.instructions.md b/AmiReel.WinUI/.github/instructions/windows-apis.instructions.md
new file mode 100644
index 0000000..002e776
--- /dev/null
+++ b/AmiReel.WinUI/.github/instructions/windows-apis.instructions.md
@@ -0,0 +1,92 @@
+---
+description: 'WinAppSDK & Windows Platform SDK -- API namespace catalog and lookup guidance'
+applyTo: '**/*.cs, **/*.xaml, **/*.csproj'
+---
+
+# Windows APIs -- WinAppSDK & Windows Platform SDK
+
+## Sample-First Rule
+
+> **Agent Rule -- MANDATORY:** Before implementing **any** WinAppSDK or Windows Platform SDK API you have not used before, you **must** search the sample repositories below for a working example first. **Do not guess API usage patterns from documentation alone** -- the docs often omit critical details that only the sample code reveals. Search **all** of the following repos, not just one:
+
+| # | Repository | What it covers |
+|---|---|---|
+| 1 | [WindowsAppSDK-Samples](https://github.com/microsoft/WindowsAppSDK-Samples) | All WinAppSDK features (AI, windowing, lifecycle, notifications, etc.) |
+| 2 | [AI Dev Gallery](https://github.com/microsoft/ai-dev-gallery) | On-device AI/ML patterns, model usage examples |
+| 3 | [WinUI-Gallery](https://github.com/microsoft/WinUI-Gallery) | UI control patterns and XAML examples |
+
+### How to apply
+
+1. **Find the right API** -- Translate the user's scenario/requirement into common API/programming keywords, then search the API references (Part A-B below) using those keywords to identify which API fits.
+2. **Search for samples** -- Once you know which API to use, search each sample repo above for the class name to find a working example.
+3. **Study the sample** -- Read the sample's Model / ViewModel / Service layer to understand how the API is actually called -- object lifetime, required parameters, data preparation, error handling.
+4. **Adapt** the sample pattern into our MVVM architecture -- don't copy the sample structure wholesale, but match its API call sequence exactly.
+
+---
+
+> **Agent Rule:** Before implementing any feature that involves a platform capability, **consult this file** to check whether a built-in API already exists. Always verify exact class names, method signatures, and availability by following the reference links -- do not guess API shapes.
+
+---
+
+## Part A -- Windows App SDK APIs
+
+**Full API reference:**
+
+> **Agent Rule:** Do not rely on a hardcoded namespace list -- the SDK is updated frequently. Instead, **search** the API reference above by converting the user's scenario into common programming keywords.
+
+### How to search
+
+1. **Translate** the user's request into API/programming terms. Examples:
+ - "I want to describe an image" -> search for: `image description`, `ImageDescription`, `describe image`
+ - "Add a notification" -> search for: `notification`, `toast`, `AppNotification`
+ - "Pick a file" -> search for: `file picker`, `StoragePicker`, `FileOpenPicker`
+ - "Make the window always on top" -> search for: `AppWindow`, `presenter`, `compact overlay`
+2. **Search** the [WinAppSDK API reference](https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/) using `web_search` or `web_fetch` with those keywords.
+3. **Verify** the class/method exists in the SDK version used by this project (check `.csproj` `` for `Microsoft.WindowsAppSDK` version).
+
+### Key reference links
+
+| # | Link | When to consult |
+|---|---|---|
+| 1 | [WinAppSDK API Reference (full)](https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/) | **Always** -- search and look up exact class/method signatures here |
+| 2 | [Windows App SDK overview](https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/) | Feature overview, architecture |
+| 3 | [Release notes (stable)](https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/stable-channel) | API availability, version support, breaking changes |
+| 4 | [Windows AI overview](https://learn.microsoft.com/en-us/windows/ai/) | All AI options: Windows AI APIs, Windows ML, Foundry Local |
+| 5 | [Get started with Windows AI APIs](https://learn.microsoft.com/en-us/windows/ai/apis/get-started) | Prerequisites, project setup, first AI call |
+| 6 | [Windows ML overview](https://learn.microsoft.com/en-us/windows/ai/new-windows-ml/overview) | Custom ONNX model inference |
+| 7 | [Foundry Local](https://learn.microsoft.com/en-us/windows/ai/foundry-local/get-started) | Run OSS LLMs locally |
+
+---
+
+## Part B -- Windows Platform SDK (UWP / WinRT APIs)
+
+**Full API reference:**
+
+> **Agent Rule:** The Platform SDK (`Windows.*` namespaces) is very large and constantly evolving. Do not rely on a hardcoded list. **Search** the API reference by translating the user's requirement into programming keywords.
+
+### How to search
+
+1. **Translate** the user's request into API/programming terms. Examples:
+ - "Send a Bluetooth message" -> search for: `Bluetooth`, `RFCOMM`, `BluetoothDevice`
+ - "Get the user's location" -> search for: `geolocation`, `Geolocator`, `position`
+ - "Read text from an image" -> search for: `OCR`, `text recognition`, `OcrEngine`
+ - "Copy to clipboard" -> search for: `clipboard`, `DataTransfer`, `DataPackage`
+2. **Search** the [Platform SDK API reference](https://learn.microsoft.com/en-us/uwp/api/) using `web_search` or `web_fetch` with those keywords.
+3. **Check for WinAppSDK equivalent** -- some Platform SDK APIs have newer equivalents in Part A. Always prefer the WinAppSDK version when both exist.
+
+### Key reference links
+
+| # | Link | When to consult |
+|---|---|---|
+| 1 | [Platform SDK API Reference (full)](https://learn.microsoft.com/en-us/uwp/api/) | **Search here** for any Windows capability not in WinAppSDK |
+| 2 | [Windows SDK downloads](https://developer.microsoft.com/windows/downloads/windows-sdk/) | SDK versions and downloads |
+
+---
+
+## Validation
+
+- Before implementing any platform feature, confirm the API is available in the current Windows App SDK version by checking the [release notes](https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/stable-channel).
+- For features requiring specific hardware (NPU), provide a graceful fallback for unsupported devices.
+- When both WinAppSDK and Platform SDK offer a similar API, prefer the WinAppSDK version.
+
+
diff --git a/AmiReel.WinUI/.github/instructions/winui-best-practices.instructions.md b/AmiReel.WinUI/.github/instructions/winui-best-practices.instructions.md
new file mode 100644
index 0000000..a8565ac
--- /dev/null
+++ b/AmiReel.WinUI/.github/instructions/winui-best-practices.instructions.md
@@ -0,0 +1,420 @@
+---
+description: 'WinUI 3 / WinAppSDK architecture, MVVM, XAML patterns, DI, theming, and controls guidance'
+applyTo: '**/*.cs, **/*.xaml, **/*.csproj'
+---
+
+# WinUI 3 / WinAppSDK -- Best Practices & Patterns
+
+This file covers WinUI 3-specific patterns, conventions, and architecture guidance for this project.
+
+---
+
+## 1. Architecture -- MVVM Pattern
+
+### Overview
+
+Use **Model-View-ViewModel (MVVM)** for all UI features:
+
+| Layer | Responsibility | Example |
+|---|---|---|
+| **Model** | Data structures & business entities | `Item.cs`, `UserProfile.cs` |
+| **View** | XAML UI -- layout, styles, animations | `MainPage.xaml` |
+| **ViewModel** | UI state, commands, data transformation | `MainViewModel.cs` |
+| **Service** | Business logic, data access, navigation | `IDataService.cs`, `NavigationService.cs` |
+
+### Project Folder Structure
+
+```
+/
+ Models/ -> Data classes
+ ViewModels/ -> ViewModels (one per page/dialog)
+ Views/ -> XAML pages and windows
+ Services/ -> Business logic & platform services
+ Converters/ -> IValueConverter implementations
+ Helpers/ -> Static utility methods
+ Controls/ -> Custom/reusable controls
+ Strings/
+ en-us/
+ Resources.resw
+ Assets/ -> Images, icons, splash screens
+```
+
+### ViewModel Base
+
+Use `CommunityToolkit.Mvvm` (recommended) for boilerplate-free ViewModels:
+
+```xml
+
+
+```
+
+```csharp
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+
+public partial class MainViewModel : ObservableObject
+{
+ [ObservableProperty]
+ private string _title = string.Empty;
+
+ [ObservableProperty]
+ private bool _isLoading;
+
+ [RelayCommand]
+ private async Task LoadDataAsync()
+ {
+ IsLoading = true;
+ try
+ {
+ // Load data
+ }
+ finally
+ {
+ IsLoading = false;
+ }
+ }
+}
+```
+
+### View-ViewModel Binding
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+```
+
+---
+
+## 2. XAML Best Practices
+
+### Use `x:Bind` Over `{Binding}`
+
+| Feature | `x:Bind` | `{Binding}` |
+|---|---|---|
+| Compile-time check | Yes | No |
+| Performance | Faster (compiled) | Slower (reflection) |
+| Default mode | OneTime | OneWay |
+| IntelliSense | Yes | No |
+
+```xml
+
+
+
+
+
+```
+
+### Use `x:Load` for Deferred Loading
+
+```xml
+
+
+
+```
+
+### Use WinUI Controls from the SDK
+
+Prefer the WinUI 3 controls from `Microsoft.UI.Xaml.Controls`, **not** the older UWP `Windows.UI.Xaml.Controls`:
+
+```csharp
+// GOOD -- WinUI 3
+using Microsoft.UI.Xaml.Controls;
+
+// AVOID -- UWP (won't work in WinUI 3 desktop)
+// using Windows.UI.Xaml.Controls;
+```
+
+### XAML Formatting Convention
+
+```xml
+
+```
+
+- One attribute per line for controls with 3+ attributes.
+- Order: `x:Name` -> `x:Uid` -> `AutomationProperties` -> layout -> data -> style.
+
+---
+
+## 3. Dependency Injection
+
+### Setup with `Microsoft.Extensions.DependencyInjection`
+
+```xml
+
+
+```
+
+```csharp
+// App.xaml.cs
+public partial class App : Application
+{
+ public static IServiceProvider Services { get; private set; } = null!;
+
+ public App()
+ {
+ InitializeComponent();
+
+ Services = ConfigureServices();
+ }
+
+ private static IServiceProvider ConfigureServices()
+ {
+ var services = new ServiceCollection();
+
+ // Services
+ services.AddSingleton();
+ services.AddTransient();
+
+ // ViewModels
+ services.AddTransient();
+
+ return services.BuildServiceProvider();
+ }
+}
+```
+
+---
+
+## 4. Navigation
+
+### Frame-based Navigation
+
+```csharp
+public interface INavigationService
+{
+ bool CanGoBack { get; }
+ void NavigateTo() where TPage : Page;
+ void GoBack();
+}
+```
+
+Use a `NavigationView` with a `Frame`:
+
+```xml
+
+
+
+
+
+
+
+```
+
+---
+
+## 5. Windowing & Title Bar
+
+### Custom Title Bar
+
+```csharp
+// In Window constructor or Activated handler
+ExtendsContentIntoTitleBar = true;
+SetTitleBar(AppTitleBar); // AppTitleBar is a UIElement in your XAML
+```
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### Window Sizing
+
+```csharp
+var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
+var windowId = Win32Interop.GetWindowIdFromWindow(hwnd);
+var appWindow = AppWindow.GetFromWindowId(windowId);
+appWindow.Resize(new SizeInt32(1280, 720));
+```
+
+---
+
+## 6. Theming
+
+### Support Light/Dark/High Contrast
+
+```xml
+
+
+
+
+
+
+
+
+```
+
+### Detect Theme Changes
+
+```csharp
+if (Content is FrameworkElement rootElement)
+{
+ rootElement.ActualThemeChanged += (s, e) =>
+ {
+ // React to theme change
+ };
+}
+```
+
+### Use Theme Resources, Not Hard-coded Colors
+
+```xml
+
+
+
+
+
+```
+
+---
+
+## 7. System Backdrop (Mica / Acrylic)
+
+This project uses **Mica** backdrop (already configured in `MainWindow.xaml`):
+
+```xml
+
+
+
+```
+
+Alternatives:
+```xml
+
+
+```
+
+---
+
+## 8. Community Toolkit
+
+### Recommended Packages
+
+```xml
+
+
+
+
+
+```
+
+### Useful Toolkit Features
+
+| Feature | Package | Use Case |
+|---|---|---|
+| `ObservableObject` | CommunityToolkit.Mvvm | ViewModel base class |
+| `RelayCommand` | CommunityToolkit.Mvvm | Command implementation |
+| `ObservableProperty` | CommunityToolkit.Mvvm | Auto INotifyPropertyChanged |
+| `SettingsCard` | CommunityToolkit.WinUI.Controls | Settings pages |
+| `IncrementalLoadingCollection` | CommunityToolkit.WinUI | Lazy-loading lists |
+
+---
+
+## 9. Common Pitfalls
+
+| Pitfall | Solution |
+|---|---|
+| Using `Windows.UI.Xaml` namespace | Use `Microsoft.UI.Xaml` for WinUI 3 |
+| Calling `Window.Current` | Not available in WinUI 3 -- pass window reference explicitly |
+| Using `CoreDispatcher` | Use `DispatcherQueue` instead |
+| `REGDB_E_CLASSNOTREG` error | Ensure Developer Mode is enabled, then re-register: run `winapp unregister` followed by `dotnet run` (or `winapp run `) to refresh the loose-layout registration |
+| Stale package state after manifest changes | Run `winapp unregister`, then `dotnet run` -- using `winapp run --clean` additionally wipes `LocalState`/settings to test first-run behavior |
+| XAML Designer crashes | Clean & rebuild; ensure platform matches (x64 vs AnyCPU) |
+| `{Binding}` not updating | Switch to `x:Bind` with `Mode=OneWay` or `Mode=TwoWay` |
+
+---
+
+## 10. Validation
+
+Build & register the MSIX package -- see **Build, Run & Deploy** in `.github/agents/Agents.md`.
+
+### Verify
+
+- Run the app and verify the changed UI renders correctly on x64.
+- Search XAML for `{Binding` -- replace with `x:Bind`.
+- Search XAML for `Foreground="#` or `Background="#` -- replace with `{ThemeResource}`.
+- Search C# for `Windows.UI.Xaml` -- replace with `Microsoft.UI.Xaml`.
+- Search C# for `Window.Current` -- replace with explicit window reference.
+- Search C# for `CoreDispatcher` -- replace with `DispatcherQueue`.
+- Test Light, Dark, and High Contrast themes.
+
+---
+
+## 11. Must Read & Research
+
+> **Agent Rule:** Before making any WinUI/WinAppSDK-related change, you **must** fetch and review the relevant references below using `fetch_webpage`. Consult the appropriate section based on the type of change. Apply what you learn -- do not skip this step.
+
+### Official Documentation
+
+| # | Reference | When to consult |
+|---|---|---|
+| 1 | [WinUI 3 Overview](https://learn.microsoft.com/en-us/windows/apps/winui/winui3/) | Starting new features, onboarding to the project |
+| 2 | [Windows App SDK](https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/) | Using SDK-specific APIs (windowing, lifecycle, activation) |
+| 3 | [WinUI 3 API Reference](https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/) | Looking up specific class/method signatures |
+| 4 | [WinUI 3 Gallery App](https://learn.microsoft.com/en-us/windows/apps/design/controls/) | Choosing controls, reviewing control patterns |
+| 5 | [AI Dev Gallery](https://github.com/microsoft/ai-dev-gallery) | On-device AI/ML integration patterns, model usage examples in WinUI |
+| 6 | [Windows App SDK Release Notes](https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/stable-channel) | Checking for breaking changes, new APIs, known issues |
+
+### Architecture & Patterns
+
+| # | Reference | When to consult |
+|---|---|---|
+| 7 | [CommunityToolkit.Mvvm Docs](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/) | Implementing ViewModels, commands, `ObservableProperty` |
+| 8 | [Dependency Injection in .NET](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection) | Registering services, constructor injection, lifetime management |
+| 9 | [Template Studio for WinUI](https://github.com/microsoft/TemplateStudio) | Scaffolding pages, navigation, project structure patterns |
+
+### Controls & Design
+
+| # | Reference | When to consult |
+|---|---|---|
+| 10 | [WinUI 3 Gallery (GitHub)](https://github.com/microsoft/WinUI-Gallery) | Example implementations of any WinUI control |
+| 11 | [Windows Community Toolkit (GitHub)](https://github.com/CommunityToolkit/Windows) | Before building custom controls -- check if the toolkit already has one |
+| 12 | [Fluent Design System](https://learn.microsoft.com/en-us/windows/apps/design/) | Spacing, typography, colour, motion, layout decisions |
+| 13 | [XAML Controls Gallery](https://apps.microsoft.com/store/detail/winui-3-gallery/9P3JFPWWDZRC) | Interactive demo of all controls and their properties |
+
+### Windows APIs & AI
+
+| # | Reference | When to consult |
+|---|---|---|
+| 14 | [Windows APIs instruction file](windows-apis.instructions.md) | **First stop** -- check if a built-in API already exists for the capability you need (AI, windowing, notifications, widgets, lifecycle, etc.) |
+| 15 | [Windows AI APIs](https://learn.microsoft.com/en-us/windows/ai/apis/) | On-device AI: Phi Silica (text gen), OCR, imaging (super-res, description, object extract, erase) |
+| 16 | [Windows ML](https://learn.microsoft.com/en-us/windows/ai/new-windows-ml/overview) | Custom ONNX model inference on CPU/GPU/NPU |
+| 17 | [Foundry Local](https://learn.microsoft.com/en-us/windows/ai/foundry-local/get-started) | Run OSS LLMs (Llama, Mistral, Phi) locally via REST API |
+| 18 | [Windows AI on Windows](https://learn.microsoft.com/en-us/windows/ai/) | AI landing page -- all AI options for Windows apps |
+
+### Samples
+
+> **Agent Rule -- MANDATORY:** Before implementing any WinAppSDK or Platform SDK API you have not used before, **search the samples repo first** and study the working example. Do not guess API usage from docs alone -- see the [Sample-First Rule](windows-apis.instructions.md#sample-first-rule) for details and known pitfalls.
+
+| # | Reference | When to consult |
+|---|---|---|
+| 19 | [Windows App SDK Samples](https://github.com/microsoft/WindowsAppSDK-Samples) | **Always search here first** before implementing any SDK API for the first time |
+| 20 | [WinUI 3 Demos](https://github.com/microsoft/WinUI-Gallery) | Reference implementations and patterns |
+| 21 | [Windows AI API Samples](https://github.com/microsoft/WindowsAppSDK-Samples/tree/main/Samples/WindowsAIFoundry/cs-winui) | AI API usage with WinUI (ImageDescription, TextRecognizer, LanguageModel, etc.) |
+
+
diff --git a/AmiReel.WinUI/.gitignore b/AmiReel.WinUI/.gitignore
new file mode 100644
index 0000000..dd39356
--- /dev/null
+++ b/AmiReel.WinUI/.gitignore
@@ -0,0 +1,54 @@
+## .NET / Visual Studio
+[Bb]in/
+[Oo]bj/
+[Dd]ebug/
+[Rr]elease/
+.vs/
+*.user
+*.suo
+*.userosscache
+*.sln.docstates
+artifacts/
+
+# Build logs
+[Ll]og/
+[Ll]ogs/
+*.log
+*.binlog
+
+# Test results
+[Tt]est[Rr]esult*/
+*.trx
+*.coverage
+*.coveragexml
+
+# NuGet
+*.nupkg
+*.snupkg
+*.nuget.props
+*.nuget.targets
+project.lock.json
+
+# MSIX packaging output
+AppPackages/
+BundleArtifacts/
+*.msix
+*.msixupload
+*.appx
+*.appxbundle
+*.appxupload
+
+# Publish output
+publish/
+*.pubxml
+PublishScripts/
+
+# Code analysis and tooling
+_ReSharper*/
+*.DotSettings.user
+*.dotCover
+.idea/
+
+# Generated files
+Generated\ Files/
+*_wpftmp.csproj
diff --git a/AmiReel.WinUI/AGENTS.md b/AmiReel.WinUI/AGENTS.md
new file mode 100644
index 0000000..d3da0bf
--- /dev/null
+++ b/AmiReel.WinUI/AGENTS.md
@@ -0,0 +1,270 @@
+# Copilot Agent Instructions -- WinUI 3 / WinAppSDK
+
+## Project Overview
+
+This is a **WinUI 3** desktop application built on the **Windows App SDK**. It uses MSIX packaging and supports x86, x64, and ARM64 architectures.
+
+> **Source of truth for versions & names:** Always read the project `.csproj` to determine the current `TargetFramework`, `RuntimeIdentifiers`, `Platforms`, `RootNamespace`, and `Microsoft.WindowsAppSDK` package version. Never hard-code project names or version numbers in instruction files.
+>
+> Throughout this document and the instruction files, `` is a placeholder -- replace it with the actual project folder/assembly name (derived from the `.csproj` filename).
+
+| Property | How to determine |
+|---|---|
+| UI Framework | WinUI 3 (`Microsoft.UI.Xaml`) -- always used |
+| App SDK | Read `Microsoft.WindowsAppSDK` version from `.csproj` `` |
+| Runtime / TFM | Read `` from `.csproj` (e.g., `net10.0-windows10.0.26100.0`) |
+| Target OS | Derived from `` and `` in `.csproj` |
+| Platforms | Read `` from `.csproj` (e.g., `x86;x64;ARM64`) |
+| Packaging | MSIX (`true`) |
+| Namespace | Read `` from `.csproj` |
+| Nullable | Read `` from `.csproj` |
+
+> **Default TFM:** Templates ship with `net10.0` by default. Pass
+> `--dotnet-version ` (for example `net10.0`) when running `dotnet new ...`
+> or edit `` inside the generated `.csproj` before the first
+> build if you need a newer framework. Keep `` synchronized
+> with the framework you pick.
+
+## Instruction Files Index
+
+All detailed agent instructions are organized under `.github/instructions/`:
+
+| File | Scope |
+|---|---|
+| [design-principles.instructions.md](.github/instructions/design-principles.instructions.md) | DRY, KISS, SOLID, YAGNI |
+| [globalization.instructions.md](.github/instructions/globalization.instructions.md) | Globalization & Localization |
+| [accessibility.instructions.md](.github/instructions/accessibility.instructions.md) | Accessibility |
+| [security.instructions.md](.github/instructions/security.instructions.md) | Security |
+| [performance.instructions.md](.github/instructions/performance.instructions.md) | Performance |
+| [code-quality.instructions.md](.github/instructions/code-quality.instructions.md) | Static Analysis, StyleCop, Code Cleanup |
+| [winui-best-practices.instructions.md](.github/instructions/winui-best-practices.instructions.md) | WinUI 3 / WinAppSDK patterns & references |
+| [windows-apis.instructions.md](.github/instructions/windows-apis.instructions.md) | WinAppSDK & Platform SDK API namespace catalog & lookup guidance |
+| [testing.instructions.md](.github/instructions/testing.instructions.md) | Unit Testing, Build & Run |
+
+## Core Agent Workflow
+
+Every time you work on this codebase, follow this checklist:
+
+### Before Writing Code
+1. **Review the original goal** -- Re-read the user's request and confirm you understand the intent.
+2. **Check existing code** -- Search for related implementations to avoid duplication (DRY).
+3. **Find the right API** -- If the task involves a platform capability (AI, UI controls, file access, notifications, windowing, widgets, sensors, etc.), first check the [Windows APIs catalog](.github/instructions/windows-apis.instructions.md) and then look up the correct API in the [WinUI 3 API Reference](https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/) before writing code.
+4. **Plan the approach** -- Consider SOLID principles and identify which classes/interfaces are involved.
+
+### While Writing Code
+
+> **Agent Rule -- MANDATORY:** Steps 5-8 are **not** passive references. You **must** actually open and read the linked instruction file before writing code that falls within its scope. Do not skip this -- these files contain rules, anti-patterns, and checklists that must be applied.
+
+5. **Apply Design Principles** -- **Read** [design-principles](.github/instructions/design-principles.instructions.md) before adding/refactoring classes or logic. Apply DRY, KISS, SOLID, YAGNI.
+6. **Follow Fundamentals** -- **Read the applicable instruction files** based on what you're changing:
+ - Adding or changing **UI controls / XAML**? -> Read [accessibility](.github/instructions/accessibility.instructions.md) (AutomationProperties, keyboard nav, contrast) AND [performance](.github/instructions/performance.instructions.md) (x:Bind, x:Load, virtualization).
+ - Adding or changing **user-facing strings** (labels, messages, tooltips)? -> Read [globalization](.github/instructions/globalization.instructions.md) (`.resw` files, `x:Uid`, `ResourceLoader`).
+ - Handling **secrets, user input, HTTP, or permissions**? -> Read [security](.github/instructions/security.instructions.md) (no hard-coded secrets, input validation, least privilege).
+ - Working on **data binding, collections, async/IO, or layout**? -> Read [performance](.github/instructions/performance.instructions.md) (x:Bind, virtualization, async patterns).
+7. **Respect Code Quality Rules** -- **Read** [code-quality](.github/instructions/code-quality.instructions.md) before writing code. Follow all CA*/SA*/IDE* analyzer rules and naming conventions.
+8. **Follow WinUI Patterns** -- **Read** [winui-best-practices](.github/instructions/winui-best-practices.instructions.md) for MVVM, x:Bind, community toolkit, and API verification.
+
+### After Writing Code
+9. **Remove unused code** -- Delete unused `using` statements, dead code, commented-out blocks.
+10. **Write unit tests** -- Every new public method/class needs tests. **Read** [testing](.github/instructions/testing.instructions.md) for framework setup, naming conventions (`MethodName_Scenario_ExpectedResult`), AAA pattern, and `dotnet test` commands.
+11. **Build the project** -- Detect the platform first (`$Platform = $env:PROCESSOR_ARCHITECTURE`), then run `dotnet build -c Debug -p:Platform=$Platform` from the project folder and fix all warnings/errors. **If build errors occur, follow the Troubleshooting Build Errors workflow below.**
+12. **Run tests** -- Run tests related to the change using `--filter` (see [testing](.github/instructions/testing.instructions.md)). Run the full suite only when the change is cross-cutting.
+13. **Run the app with package identity** -- Use `dotnet run` (preferred -- the project references `Microsoft.Windows.SDK.BuildTools.WinApp`, which automatically invokes `winapp run` to register a loose-layout package and launch via AUMID). See [Build, Run & Deploy](#build-run--deploy) below for advanced scenarios.
+14. **Re-review against original goal** -- Confirm the implementation matches the user's request.
+
+### Troubleshooting Build Errors
+
+> **Agent Rule -- MANDATORY:** When a build fails due to an unknown type, missing namespace, unresolved API, or similar definition error, follow this escalation order. **Do NOT jump straight to reading `.winmd` files or using `ildasm`/decompilers** -- always try web search first.
+
+**Step 1 -- Web Search (ALWAYS try first):**
+1. Open and read [windows-apis.instructions.md](.github/instructions/windows-apis.instructions.md) -- it contains the API namespace catalog and lookup guidance.
+2. Translate the unknown type/namespace into search keywords (e.g., `ImageDescription` -> "WinAppSDK ImageDescription API").
+3. Use `web_search` or `web_fetch` to search the [WinAppSDK API Reference](https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/) and the [Platform SDK API Reference](https://learn.microsoft.com/en-us/uwp/api/) for the correct namespace, class name, and method signatures.
+4. Check the [release notes](https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/stable-channel) to verify the API is available in the project's SDK version (read from `.csproj`).
+
+**Step 2 -- Sample Repos:**
+If web search finds the API but usage is unclear, search the sample repositories listed in [windows-apis.instructions.md](.github/instructions/windows-apis.instructions.md) for working examples.
+
+**Step 3 -- WinMD / Decompiler (last resort only):**
+Only if Steps 1-2 fail to resolve the issue, then inspect `.winmd` metadata files or use decompilation tools to discover the exact type definitions. This is a fallback, not the default approach.
+
+## Build, Run & Deploy
+
+This is an MSIX-packaged WinUI 3 app. You **must** pass both `-c` (Configuration) and `-p:Platform=` to every `dotnet build`/`dotnet test` command.
+
+This template references the [`Microsoft.Windows.SDK.BuildTools.WinApp`](https://www.nuget.org/packages/Microsoft.Windows.SDK.BuildTools.WinApp) NuGet package, which hooks `dotnet run` to invoke the [`winapp` CLI](https://github.com/microsoft/WinAppCli). Use `dotnet run` for everyday inner-loop development -- you do not need to call `Add-AppxPackage` or `MakeAppx.exe` by hand.
+
+### Dotnet CLI Workflow
+
+- Prefer `dotnet new` for scaffolding projects and items so namespaces, GUIDs,
+ and resource wiring stay correct.
+- Common commands:
+ - `dotnet new winui -n MyApp`
+ - `dotnet new winui-page -n SettingsPage --project .\MyApp\MyApp.csproj`
+ - `dotnet new winui-usercontrol -n ProfileCard --project .\MyApp\MyApp.csproj`
+- Discover available scaffolds with `dotnet new winui --list` (shows supported
+ parameters such as `--dotnet-version`).
+- Need a newer TFM? Supply `--dotnet-version net10.0` during scaffold or edit
+ `` afterward before the first build.
+
+### Prerequisites
+
+- **Developer Mode must be enabled** on Windows. Verify with:
+ ```powershell
+ # Check developer mode
+ Get-WindowsDeveloperLicense
+ # If not enabled: Settings -> System -> For developers -> Developer Mode -> On
+ ```
+- **`winapp` CLI** -- installed transitively via the `Microsoft.Windows.SDK.BuildTools.WinApp` NuGet reference (no separate install needed for `dotnet run`). To use `winapp` directly from the terminal for advanced scenarios (manifest editing, certificate management, packaging), install it standalone:
+ ```powershell
+ winget install Microsoft.WinAppCli --source winget
+ ```
+
+### Detect Platform
+
+**Always detect the machine's architecture first** -- never hardcode a platform value. Run this once at the start of every build/test session:
+
+```powershell
+# Detect the current machine's CPU architecture
+# (returns AMD64 on x64 boxes, ARM64 on ARM64 boxes, x86 on 32-bit boxes)
+$arch = $env:PROCESSOR_ARCHITECTURE
+$Platform = if ($arch -eq 'AMD64') { 'x64' } else { $arch } # MSBuild expects x64/x86/ARM64
+```
+
+Use `$Platform` in all subsequent `dotnet` commands.
+
+### Build
+
+```powershell
+# Run from the project folder containing the .csproj
+cd
+
+# Detect platform (see above)
+$arch = $env:PROCESSOR_ARCHITECTURE
+$Platform = if ($arch -eq 'AMD64') { 'x64' } else { $arch }
+
+# Debug build (matches current machine)
+dotnet build -c Debug -p:Platform=$Platform
+
+# Release build
+dotnet build -c Release -p:Platform=$Platform
+```
+
+### Run with Package Identity (preferred)
+
+The template references `Microsoft.Windows.SDK.BuildTools.WinApp`, which makes `dotnet run` register a loose-layout package via `winapp run` and launch the app via AUMID activation -- giving it the same package identity it would have in production:
+
+```powershell
+$arch = $env:PROCESSOR_ARCHITECTURE
+$Platform = if ($arch -eq 'AMD64') { 'x64' } else { $arch }
+
+dotnet run -c Debug -p:Platform=$Platform
+```
+
+The CLI prints the registered package's AUMID and the launched process's PID -- attach a debugger to that PID for runtime debugging.
+
+#### Useful MSBuild knobs (set in `.csproj` ``)
+
+| Property | When to set |
+|---|---|
+| `EnableWinAppRunSupport=false` | Disable the `dotnet run` integration entirely (e.g., to launch unpackaged) |
+| `WinAppRunUseExecutionAlias=true` | For console apps -- launches via `uap5:ExecutionAlias` so stdin/stdout stay in the terminal. Add the alias first with `winapp manifest add-alias`. |
+| `WinAppRunNoLaunch=true` | Register the package but don't launch (attach your IDE's debugger before launch) |
+| `WinAppLaunchArgs="--flag value"` | Pass arguments to the app on launch |
+
+#### Manual `winapp run` (when not using `dotnet run`)
+
+```powershell
+# Read from .csproj first; example uses net10.0-windows10.0.26100.0
+winapp run .\bin\$Platform\Debug\
+
+# Pass args after -- to avoid escaping
+winapp run .\bin\$Platform\Debug\ -- --my-flag value
+
+# Console app: keep stdin/stdout in the current terminal (requires uap5:ExecutionAlias)
+winapp run .\bin\$Platform\Debug\ --with-alias
+
+# Wipe LocalState/settings between runs to test first-run behavior
+winapp run .\bin\$Platform\Debug\ --clean
+```
+
+#### Run Tests
+
+```powershell
+# Run from the test project folder
+cd .Tests
+$arch = $env:PROCESSOR_ARCHITECTURE
+$Platform = if ($arch -eq 'AMD64') { 'x64' } else { $arch }
+dotnet test -c Debug -p:Platform=$Platform
+```
+
+### winapp CLI command reference
+
+The `winapp` CLI is the canonical entry point for app-identity, packaging, certificate, and asset operations. Reach for it instead of hand-rolling `MakeAppx`/`SignTool`/`Add-AppxPackage` invocations.
+
+| Scenario | Command | Notes |
+|---|---|---|
+| **Run/debug with identity (loose layout)** | `dotnet run` (or `winapp run `) | Default for inner loop. Registers full loose-layout package. |
+| **Console app inner loop** | Set `WinAppRunUseExecutionAlias=true` in `.csproj`, then `dotnet run` | Requires `uap5:ExecutionAlias` -- add via `winapp manifest add-alias`. |
+| **Sparse identity on a single exe** | `winapp create-debug-identity .\bin\Debug\\.exe` | Use when the exe is outside the build folder, or for IDE F5 startup debugging. |
+| **Stop debugging / clean up** | `winapp unregister` | Removes dev packages registered for the current project. |
+| **Generate dev signing cert** | `winapp cert generate --manifest .\Package.appxmanifest --install` | Reads publisher from manifest. Stored as `devcert.pfx` in the project. |
+| **Inspect a cert** | `winapp cert info .\devcert.pfx` | Verify subject matches manifest publisher. |
+| **Sign a file** | `winapp sign .\MyApp.msix --cert .\devcert.pfx` | Wraps `signtool`. |
+| **Build distribution MSIX** | `winapp pack .\bin\$Platform\Release\\win- --cert .\devcert.pfx` | Auto-resolves `$targetnametoken$`, registers third-party WinRT components. |
+| **Self-contained MSIX (bundles WinAppSDK)** | `winapp pack ... --self-contained` | No runtime dependency on the framework package. |
+| **Regenerate Square44/Square150/etc. icons** | `winapp manifest update-assets .\branding\logo.svg` | SVG preferred -- rendered at all 5 scale and 14 targetsize variants. |
+| **Add execution alias** | `winapp manifest add-alias` | Required for console-app inline I/O via `WinAppRunUseExecutionAlias`. |
+| **Underlying SDK tools** | `winapp tool signtool ...`, `winapp tool makeappx ...` | Falls back to the raw [`Microsoft.Windows.SDK.BuildTools`](https://www.nuget.org/packages/Microsoft.Windows.SDK.BuildTools/) tools when needed. |
+
+For full reference, see the [winapp CLI usage docs](https://github.com/microsoft/WinAppCli/blob/main/docs/usage.md) and the [Debugging Guide](https://github.com/microsoft/WinAppCli/blob/main/docs/debugging.md).
+
+### Fallback: register a loose layout manually
+
+Only use this if you've explicitly disabled the `winapp` integration (`false`) or are debugging the deployment itself. The supported path is `dotnet run` / `winapp run`.
+
+```powershell
+$arch = $env:PROCESSOR_ARCHITECTURE
+$Platform = if ($arch -eq 'AMD64') { 'x64' } else { $arch }
+$Rid = $Platform.ToLower() # arm64, x64, x86
+
+# Register the built MSIX package from the build output
+# Read from .csproj to build the correct path.
+Add-AppxPackage -Register ".\\bin\$Platform\Debug\\win-$Rid\AppxManifest.xml"
+```
+
+> **Note:** Replace `` with the actual value from `.csproj` (e.g., `net10.0-windows10.0.26100.0`).
+
+If the launch fails because an old instance is still running, terminate it with `taskkill /IM .exe /F` before re-running.
+
+## Key Rules (Always Enforced)
+
+- **Every change must build and pass tests** -- Run `dotnet build` and `dotnet test` (see [Build, Run & Deploy](#build-run--deploy)) before considering any task complete.
+- **Follow all instruction files** -- The detailed rules in `.github/instructions/` are authoritative. **You must actually open and read them** (not just acknowledge they exist) when working within their scope. See the trigger conditions in steps 5-8 above.
+- **Web search before decompilation** -- When facing unknown types or build errors, always search the web / API docs first. Only use WinMD/ILDASM as a last resort (see [Troubleshooting Build Errors](#troubleshooting-build-errors)).
+- **Use `winapp` for app-identity / packaging / signing** -- Don't hand-roll `MakeAppx`/`SignTool`/`Add-AppxPackage` invocations. The CLI keeps the manifest, certificate, and registration steps in sync.
+
+## Windows AI Prerequisites
+
+When integrating Windows AI APIs (Phi Silica, Windows Vision -- ImageDescription,
+TextRecognizer, ImageScaler, etc.) -- see
+[windows-apis.instructions.md](.github/instructions/windows-apis.instructions.md):
+
+1. **Package identity is required.** All Windows AI APIs require the app to
+ run with package identity. The `dotnet run` flow described above already
+ provides this. If you're testing outside `dotnet run`, register identity
+ first with `winapp run` or `winapp create-debug-identity`.
+2. **Manifest capabilities.** Add the capabilities each API requires to
+ `Package.appxmanifest` (commonly `runFullTrust`; some scenarios additionally
+ need `internetClient`). Check the API's docs page for the exact list.
+3. **Hardware / OS gating.** Some APIs require a Copilot+ PC (NPU) or a
+ minimum Windows build. Always probe availability with the API's
+ `IsAvailable` / `EnsureReadyAsync` pattern (or equivalent) and provide a
+ graceful fallback for unsupported devices.
+4. **Verify locally before checking in.** After capability or manifest
+ changes, re-run `dotnet run` (or `winapp run`) so the registered identity
+ reflects the updated manifest -- a stale registration will silently use
+ the old capability set.
+
+
+
diff --git a/AmiReel.WinUI/AmiReel.WinUI.csproj b/AmiReel.WinUI/AmiReel.WinUI.csproj
new file mode 100644
index 0000000..972a551
--- /dev/null
+++ b/AmiReel.WinUI/AmiReel.WinUI.csproj
@@ -0,0 +1,84 @@
+
+
+ WinExe
+ net10.0-windows10.0.26100.0
+ 10.0.17763.0
+ AmiReel_WinUI
+ AmiReel.WinUI
+ app.manifest
+ x86;x64;ARM64
+ win-$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture.ToString().ToLowerInvariant())
+ win-$(Platform).pubxml
+ true
+ false
+ true
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+ False
+ True
+ False
+ True
+
+
diff --git a/AmiReel.WinUI/App.xaml b/AmiReel.WinUI/App.xaml
new file mode 100644
index 0000000..a294467
--- /dev/null
+++ b/AmiReel.WinUI/App.xaml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AmiReel.WinUI/App.xaml.cs b/AmiReel.WinUI/App.xaml.cs
new file mode 100644
index 0000000..96e1495
--- /dev/null
+++ b/AmiReel.WinUI/App.xaml.cs
@@ -0,0 +1,47 @@
+using Windows.ApplicationModel;
+using Windows.ApplicationModel.Activation;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Controls.Primitives;
+using Microsoft.UI.Xaml.Data;
+using Microsoft.UI.Xaml.Input;
+using Microsoft.UI.Xaml.Media;
+using Microsoft.UI.Xaml.Navigation;
+using Microsoft.UI.Xaml.Shapes;
+using WinRT.Interop;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+namespace AmiReel_WinUI;
+
+///
+/// Provides application-specific behavior to supplement the default Application class.
+///
+public partial class App : Application
+{
+ private Window? _window;
+ public static IntPtr MainWindowHandle { get; private set; }
+
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ InitializeComponent();
+ }
+
+ ///
+ /// Invoked when the application is launched.
+ ///
+ /// Details about the launch request and process.
+ protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
+ {
+ _window = new MainWindow();
+ MainWindowHandle = WindowNative.GetWindowHandle(_window);
+ _window.Activate();
+ }
+}
diff --git a/AmiReel.WinUI/Assets/AppIcon.ico b/AmiReel.WinUI/Assets/AppIcon.ico
new file mode 100644
index 0000000..235dad2
Binary files /dev/null and b/AmiReel.WinUI/Assets/AppIcon.ico differ
diff --git a/AmiReel.WinUI/Assets/LockScreenLogo.scale-200.png b/AmiReel.WinUI/Assets/LockScreenLogo.scale-200.png
new file mode 100644
index 0000000..33f889e
Binary files /dev/null and b/AmiReel.WinUI/Assets/LockScreenLogo.scale-200.png differ
diff --git a/AmiReel.WinUI/Assets/SplashScreen.scale-200.png b/AmiReel.WinUI/Assets/SplashScreen.scale-200.png
new file mode 100644
index 0000000..802c79d
Binary files /dev/null and b/AmiReel.WinUI/Assets/SplashScreen.scale-200.png differ
diff --git a/AmiReel.WinUI/Assets/Square150x150Logo.scale-200.png b/AmiReel.WinUI/Assets/Square150x150Logo.scale-200.png
new file mode 100644
index 0000000..ddba42a
Binary files /dev/null and b/AmiReel.WinUI/Assets/Square150x150Logo.scale-200.png differ
diff --git a/AmiReel.WinUI/Assets/Square44x44Logo.scale-200.png b/AmiReel.WinUI/Assets/Square44x44Logo.scale-200.png
new file mode 100644
index 0000000..9327dd7
Binary files /dev/null and b/AmiReel.WinUI/Assets/Square44x44Logo.scale-200.png differ
diff --git a/AmiReel.WinUI/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/AmiReel.WinUI/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644
index 0000000..e51416a
Binary files /dev/null and b/AmiReel.WinUI/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/AmiReel.WinUI/Assets/Square44x44Logo.targetsize-48_altform-lightunplated.png b/AmiReel.WinUI/Assets/Square44x44Logo.targetsize-48_altform-lightunplated.png
new file mode 100644
index 0000000..bf063eb
Binary files /dev/null and b/AmiReel.WinUI/Assets/Square44x44Logo.targetsize-48_altform-lightunplated.png differ
diff --git a/AmiReel.WinUI/Assets/StoreLogo.png b/AmiReel.WinUI/Assets/StoreLogo.png
new file mode 100644
index 0000000..be865f0
Binary files /dev/null and b/AmiReel.WinUI/Assets/StoreLogo.png differ
diff --git a/AmiReel.WinUI/Assets/Wide310x150Logo.scale-200.png b/AmiReel.WinUI/Assets/Wide310x150Logo.scale-200.png
new file mode 100644
index 0000000..314d297
Binary files /dev/null and b/AmiReel.WinUI/Assets/Wide310x150Logo.scale-200.png differ
diff --git a/AmiReel.WinUI/MainPage.xaml b/AmiReel.WinUI/MainPage.xaml
new file mode 100644
index 0000000..34eddda
--- /dev/null
+++ b/AmiReel.WinUI/MainPage.xaml
@@ -0,0 +1,182 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AmiReel.WinUI/MainPage.xaml.cs b/AmiReel.WinUI/MainPage.xaml.cs
new file mode 100644
index 0000000..8ef3eff
--- /dev/null
+++ b/AmiReel.WinUI/MainPage.xaml.cs
@@ -0,0 +1,293 @@
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Globalization;
+using AmigaDB.VideoRenderer.Models;
+using AmigaDB.VideoRenderer.Services;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.Win32;
+using Windows.Storage.Pickers;
+using WinRT.Interop;
+
+namespace AmiReel_WinUI;
+
+public sealed partial class MainPage : Page
+{
+ private readonly ObservableCollection _inputs = [];
+ private readonly AppSettings _loadedSettings;
+ private CancellationTokenSource? _renderCancellation;
+
+ public MainPage()
+ {
+ InitializeComponent();
+ InputList.ItemsSource = _inputs;
+ _loadedSettings = AppSettingsStore.Load();
+ OutputFolderBox.Text = _loadedSettings.OutputFolder;
+ EndCardBox.Text = _loadedSettings.EndCardPath;
+ FfmpegPathBox.Text = _loadedSettings.FfmpegPath;
+ FfprobePathBox.Text = _loadedSettings.FfprobePath;
+ OutputNameBox.Text = "amigadb_intro";
+ TrimBox.Text = _loadedSettings.TrimStart;
+ FadeBox.Text = _loadedSettings.FadeSeconds;
+ HoldBox.Text = _loadedSettings.EndCardHoldSeconds;
+ IntervalBox.Text = _loadedSettings.ThumbnailInterval;
+ SelectEncoder(_loadedSettings.Encoder);
+ SelectTheme(_loadedSettings.Theme);
+ ApplyTheme(_loadedSettings.Theme);
+ }
+
+ private async void AddInputs_Click(object sender, RoutedEventArgs e)
+ {
+ FileOpenPicker picker = CreateFileOpenPicker();
+ picker.FileTypeFilter.Add(".avi");
+ picker.SuggestedStartLocation = PickerLocationId.VideosLibrary;
+ var files = await picker.PickMultipleFilesAsync();
+ if (files is null) return;
+
+ foreach (string path in files.Select(file => file.Path).OrderBy(NaturalKey))
+ if (!_inputs.Contains(path, StringComparer.OrdinalIgnoreCase)) _inputs.Add(path);
+ }
+
+ private void ClearInputs_Click(object sender, RoutedEventArgs e) => _inputs.Clear();
+
+ private async void BrowseEndCard_Click(object sender, RoutedEventArgs e)
+ {
+ FileOpenPicker picker = CreateFileOpenPicker();
+ picker.FileTypeFilter.Add(".png");
+ picker.FileTypeFilter.Add(".jpg");
+ picker.FileTypeFilter.Add(".jpeg");
+ picker.FileTypeFilter.Add(".webp");
+ picker.FileTypeFilter.Add(".bmp");
+ var file = await picker.PickSingleFileAsync();
+ if (file is not null)
+ EndCardBox.Text = file.Path;
+ }
+
+ private async void BrowseOutput_Click(object sender, RoutedEventArgs e)
+ {
+ FolderPicker picker = CreateFolderPicker();
+ var folder = await picker.PickSingleFolderAsync();
+ if (folder is not null)
+ OutputFolderBox.Text = folder.Path;
+ }
+
+ private async void BrowseFfmpeg_Click(object sender, RoutedEventArgs e)
+ {
+ FileOpenPicker picker = CreateFileOpenPicker();
+ picker.FileTypeFilter.Add(".exe");
+ var file = await picker.PickSingleFileAsync();
+ if (file is not null)
+ FfmpegPathBox.Text = file.Path;
+ }
+
+ private async void BrowseFfprobe_Click(object sender, RoutedEventArgs e)
+ {
+ FileOpenPicker picker = CreateFileOpenPicker();
+ picker.FileTypeFilter.Add(".exe");
+ var file = await picker.PickSingleFileAsync();
+ if (file is not null)
+ FfprobePathBox.Text = file.Path;
+ }
+
+ private async void OpenSettings_Click(object sender, RoutedEventArgs e)
+ {
+ SettingsDialog.XamlRoot = XamlRoot;
+ await SettingsDialog.ShowAsync();
+ SaveSettings();
+ }
+
+ private async void Render_Click(object sender, RoutedEventArgs e)
+ {
+ try
+ {
+ RenderSettings settings = ReadSettings();
+ SaveSettings();
+ SetRendering(true);
+ LogBox.Text = string.Empty;
+ _renderCancellation = new CancellationTokenSource();
+ Progress progress = new(UpdateProgress);
+ await new RenderPipeline().RenderAsync(settings, progress, AppendLog, _renderCancellation.Token);
+ OpenOutputButton.IsEnabled = true;
+ await ShowMessageAsync("Render complete", "The AmiReel render completed successfully.");
+ }
+ catch (OperationCanceledException)
+ {
+ UpdateProgress(new(RenderProgressBar.Value, "Cancelled", "The render was cancelled."));
+ }
+ catch (Exception exception)
+ {
+ AppendLog("ERROR: " + exception);
+ StageText.Text = "Failed";
+ await ShowMessageAsync("Render failed", exception.Message);
+ }
+ finally
+ {
+ _renderCancellation?.Dispose();
+ _renderCancellation = null;
+ SetRendering(false);
+ }
+ }
+
+ private void Cancel_Click(object sender, RoutedEventArgs e)
+ {
+ CancelButton.IsEnabled = false;
+ StatusText.Text = "Stopping FFmpeg...";
+ _renderCancellation?.Cancel();
+ }
+
+ private void OpenOutput_Click(object sender, RoutedEventArgs e)
+ {
+ if (Directory.Exists(OutputFolderBox.Text))
+ Process.Start(new ProcessStartInfo("explorer.exe", OutputFolderBox.Text) { UseShellExecute = true });
+ }
+
+ private void ThemeBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (!IsLoaded) return;
+ string theme = SelectedTheme();
+ ApplyTheme(theme);
+ SaveSettings();
+ }
+
+ private RenderSettings ReadSettings()
+ {
+ static double Number(string text, string name)
+ {
+ if (!double.TryParse(text.Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out double value) || value < 0)
+ throw new ArgumentException($"Enter a valid non-negative value for {name}.");
+ return value;
+ }
+
+ if (!int.TryParse(IntervalBox.Text, out int interval) || interval < 1)
+ throw new ArgumentException("Thumbnail interval must be at least one second.");
+
+ EncoderMode encoder = Enum.Parse(SelectedEncoder());
+ return new(
+ _inputs.ToList(),
+ EndCardBox.Text.Trim(),
+ OutputFolderBox.Text.Trim(),
+ OutputNameBox.Text.Trim(),
+ FfmpegPathBox.Text.Trim(),
+ FfprobePathBox.Text.Trim(),
+ encoder,
+ Number(TrimBox.Text, "trim start"),
+ Number(FadeBox.Text, "fade"),
+ Number(HoldBox.Text, "end-card hold"),
+ interval);
+ }
+
+ private void SetRendering(bool rendering)
+ {
+ RenderButton.IsEnabled = !rendering;
+ CancelButton.IsEnabled = rendering;
+ OpenSettingsButton.IsEnabled = !rendering;
+ }
+
+ private void UpdateProgress(RenderProgress value)
+ {
+ RenderProgressBar.Value = value.Percent;
+ PercentText.Text = $"{value.Percent:0}%";
+ StageText.Text = value.Stage;
+ StatusText.Text = value.Message;
+ }
+
+ private void AppendLog(string line)
+ {
+ _ = DispatcherQueue.TryEnqueue(() =>
+ {
+ LogBox.Text += line + Environment.NewLine;
+ LogBox.Select(LogBox.Text.Length, 0);
+ });
+ }
+
+ private void SaveSettings()
+ {
+ AppSettingsStore.Save(new AppSettings(
+ OutputFolderBox.Text.Trim(),
+ EndCardBox.Text.Trim(),
+ FfmpegPathBox.Text.Trim(),
+ FfprobePathBox.Text.Trim(),
+ string.Empty,
+ SelectedTheme(),
+ SelectedEncoder(),
+ TrimBox.Text.Trim(),
+ FadeBox.Text.Trim(),
+ HoldBox.Text.Trim(),
+ IntervalBox.Text.Trim()));
+ }
+
+ private string SelectedTheme() => (ThemeBox.SelectedItem as ComboBoxItem)?.Tag?.ToString() ?? "Dark";
+
+ private string SelectedEncoder() => (EncoderBox.SelectedItem as ComboBoxItem)?.Tag?.ToString() ?? "Auto";
+
+ private void SelectTheme(string theme)
+ {
+ foreach (ComboBoxItem item in ThemeBox.Items)
+ {
+ if (string.Equals(item.Tag?.ToString(), theme, StringComparison.OrdinalIgnoreCase))
+ {
+ ThemeBox.SelectedItem = item;
+ return;
+ }
+ }
+
+ ThemeBox.SelectedIndex = 0;
+ }
+
+ private void SelectEncoder(string encoder)
+ {
+ foreach (ComboBoxItem item in EncoderBox.Items)
+ {
+ if (string.Equals(item.Tag?.ToString(), encoder, StringComparison.OrdinalIgnoreCase))
+ {
+ EncoderBox.SelectedItem = item;
+ return;
+ }
+ }
+
+ EncoderBox.SelectedIndex = 0;
+ }
+
+ private void ApplyTheme(string theme)
+ {
+ RequestedTheme = string.Equals(theme, "Light", StringComparison.OrdinalIgnoreCase)
+ ? ElementTheme.Light
+ : ElementTheme.Dark;
+ }
+
+ private FileOpenPicker CreateFileOpenPicker()
+ {
+ FileOpenPicker picker = new();
+ InitializeWithWindow.Initialize(picker, App.MainWindowHandle);
+ return picker;
+ }
+
+ private FolderPicker CreateFolderPicker()
+ {
+ FolderPicker picker = new();
+ picker.FileTypeFilter.Add("*");
+ InitializeWithWindow.Initialize(picker, App.MainWindowHandle);
+ return picker;
+ }
+
+ private static string NaturalKey(string path)
+ {
+ string name = Path.GetFileNameWithoutExtension(path);
+ int underscore = name.LastIndexOf('_');
+ return underscore >= 0 && int.TryParse(name[(underscore + 1)..], out int number)
+ ? name[..underscore] + number.ToString("D10")
+ : name;
+ }
+
+ private async Task ShowMessageAsync(string title, string message)
+ {
+ ContentDialog dialog = new()
+ {
+ Title = title,
+ Content = message,
+ CloseButtonText = "OK",
+ XamlRoot = XamlRoot
+ };
+ await dialog.ShowAsync();
+ }
+}
diff --git a/AmiReel.WinUI/MainWindow.xaml b/AmiReel.WinUI/MainWindow.xaml
new file mode 100644
index 0000000..b8f0d5d
--- /dev/null
+++ b/AmiReel.WinUI/MainWindow.xaml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AmiReel.WinUI/MainWindow.xaml.cs b/AmiReel.WinUI/MainWindow.xaml.cs
new file mode 100644
index 0000000..9598beb
--- /dev/null
+++ b/AmiReel.WinUI/MainWindow.xaml.cs
@@ -0,0 +1,27 @@
+using Microsoft.UI.Xaml;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+namespace AmiReel_WinUI;
+
+///
+/// The application window. This hosts a Frame that displays pages. Add your
+/// UI and logic to MainPage.xaml / MainPage.xaml.cs instead of here so you
+/// can use Page features such as navigation events and the Loaded lifecycle.
+///
+public sealed partial class MainWindow : Window
+{
+ public MainWindow()
+ {
+ InitializeComponent();
+
+ ExtendsContentIntoTitleBar = true;
+ SetTitleBar(AppTitleBar);
+
+ AppWindow.SetIcon("Assets/AppIcon.ico");
+
+ // Navigate the root frame to the main page on startup.
+ RootFrame.Navigate(typeof(MainPage));
+ }
+}
diff --git a/AmiReel.WinUI/Package.appxmanifest b/AmiReel.WinUI/Package.appxmanifest
new file mode 100644
index 0000000..55df9d3
--- /dev/null
+++ b/AmiReel.WinUI/Package.appxmanifest
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+ AmiReel.WinUI
+ AppPublisher
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AmiReel.WinUI/Properties/launchSettings.json b/AmiReel.WinUI/Properties/launchSettings.json
new file mode 100644
index 0000000..cc6fcd1
--- /dev/null
+++ b/AmiReel.WinUI/Properties/launchSettings.json
@@ -0,0 +1,10 @@
+{
+ "profiles": {
+ "AmiReel.WinUI (Package)": {
+ "commandName": "MsixPackage"
+ },
+ "AmiReel.WinUI (Unpackaged)": {
+ "commandName": "Project"
+ }
+ }
+}
\ No newline at end of file
diff --git a/AmiReel.WinUI/app.manifest b/AmiReel.WinUI/app.manifest
new file mode 100644
index 0000000..c9b4b0c
--- /dev/null
+++ b/AmiReel.WinUI/app.manifest
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PerMonitorV2
+
+
+
\ No newline at end of file
diff --git a/AmigaDB.VideoRenderer.csproj b/AmigaDB.VideoRenderer.csproj
index 8172c39..31e8fea 100644
--- a/AmigaDB.VideoRenderer.csproj
+++ b/AmigaDB.VideoRenderer.csproj
@@ -18,6 +18,14 @@
true
+
+
+
+
+
+
+
+
diff --git a/AmigaDB.VideoRenderer.sln b/AmigaDB.VideoRenderer.sln
index f096f80..0aaaa1d 100644
--- a/AmigaDB.VideoRenderer.sln
+++ b/AmigaDB.VideoRenderer.sln
@@ -1,19 +1,48 @@
-
+
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AmigaDB.VideoRenderer", "AmigaDB.VideoRenderer.csproj", "{7DF2BA53-4031-45EA-B4EF-27B86111FBFA}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AmiReel.WinUI", "AmiReel.WinUI\AmiReel.WinUI.csproj", "{7F1B3264-98F5-4B31-812F-7A4FB2A447BB}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7DF2BA53-4031-45EA-B4EF-27B86111FBFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7DF2BA53-4031-45EA-B4EF-27B86111FBFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7DF2BA53-4031-45EA-B4EF-27B86111FBFA}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {7DF2BA53-4031-45EA-B4EF-27B86111FBFA}.Debug|x64.Build.0 = Debug|Any CPU
+ {7DF2BA53-4031-45EA-B4EF-27B86111FBFA}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7DF2BA53-4031-45EA-B4EF-27B86111FBFA}.Debug|x86.Build.0 = Debug|Any CPU
{7DF2BA53-4031-45EA-B4EF-27B86111FBFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7DF2BA53-4031-45EA-B4EF-27B86111FBFA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7DF2BA53-4031-45EA-B4EF-27B86111FBFA}.Release|x64.ActiveCfg = Release|Any CPU
+ {7DF2BA53-4031-45EA-B4EF-27B86111FBFA}.Release|x64.Build.0 = Release|Any CPU
+ {7DF2BA53-4031-45EA-B4EF-27B86111FBFA}.Release|x86.ActiveCfg = Release|Any CPU
+ {7DF2BA53-4031-45EA-B4EF-27B86111FBFA}.Release|x86.Build.0 = Release|Any CPU
+ {7F1B3264-98F5-4B31-812F-7A4FB2A447BB}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {7F1B3264-98F5-4B31-812F-7A4FB2A447BB}.Debug|Any CPU.Build.0 = Debug|x86
+ {7F1B3264-98F5-4B31-812F-7A4FB2A447BB}.Debug|x64.ActiveCfg = Debug|x64
+ {7F1B3264-98F5-4B31-812F-7A4FB2A447BB}.Debug|x64.Build.0 = Debug|x64
+ {7F1B3264-98F5-4B31-812F-7A4FB2A447BB}.Debug|x86.ActiveCfg = Debug|x86
+ {7F1B3264-98F5-4B31-812F-7A4FB2A447BB}.Debug|x86.Build.0 = Debug|x86
+ {7F1B3264-98F5-4B31-812F-7A4FB2A447BB}.Release|Any CPU.ActiveCfg = Release|x86
+ {7F1B3264-98F5-4B31-812F-7A4FB2A447BB}.Release|Any CPU.Build.0 = Release|x86
+ {7F1B3264-98F5-4B31-812F-7A4FB2A447BB}.Release|x64.ActiveCfg = Release|x64
+ {7F1B3264-98F5-4B31-812F-7A4FB2A447BB}.Release|x64.Build.0 = Release|x64
+ {7F1B3264-98F5-4B31-812F-7A4FB2A447BB}.Release|x86.ActiveCfg = Release|x86
+ {7F1B3264-98F5-4B31-812F-7A4FB2A447BB}.Release|x86.Build.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
diff --git a/MainWindow.xaml b/MainWindow.xaml
index 7c56875..5572730 100644
--- a/MainWindow.xaml
+++ b/MainWindow.xaml
@@ -1,7 +1,7 @@
@@ -22,183 +22,185 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs
index 1c3f99f..3086ce2 100644
--- a/MainWindow.xaml.cs
+++ b/MainWindow.xaml.cs
@@ -16,6 +16,7 @@ public partial class MainWindow : Window
private readonly ObservableCollection _inputs = [];
private readonly AppSettings _loadedSettings;
private CancellationTokenSource? _renderCancellation;
+ private string? _lastRenderedFile;
public MainWindow()
{
@@ -26,6 +27,7 @@ public partial class MainWindow : Window
EndCardBox.Text = _loadedSettings.EndCardPath;
FfmpegPathBox.Text = _loadedSettings.FfmpegPath;
FfprobePathBox.Text = _loadedSettings.FfprobePath;
+ PreviewPlayerPathBox.Text = _loadedSettings.PreviewPlayerPath;
TrimBox.Text = _loadedSettings.TrimStart;
FadeBox.Text = _loadedSettings.FadeSeconds;
HoldBox.Text = _loadedSettings.EndCardHoldSeconds;
@@ -70,6 +72,12 @@ public partial class MainWindow : Window
if (dialog.ShowDialog(this) == true) FfprobePathBox.Text = dialog.FileName;
}
+ private void BrowsePreviewPlayer_Click(object sender, RoutedEventArgs e)
+ {
+ OpenFileDialog dialog = new() { Filter = "Video player (*.exe)|*.exe|All files|*.*" };
+ if (dialog.ShowDialog(this) == true) PreviewPlayerPathBox.Text = dialog.FileName;
+ }
+
private void OpenSettings_Click(object sender, RoutedEventArgs e) => SettingsOverlay.Visibility = Visibility.Visible;
private void CloseSettings_Click(object sender, RoutedEventArgs e)
@@ -89,7 +97,9 @@ public partial class MainWindow : Window
_renderCancellation = new CancellationTokenSource();
Progress progress = new(UpdateProgress);
await new RenderPipeline().RenderAsync(settings, progress, AppendLog, _renderCancellation.Token);
+ _lastRenderedFile = Path.Combine(settings.OutputDirectory, settings.OutputName + "_final.mp4");
OpenOutputButton.IsEnabled = true;
+ PreviewRenderedButton.IsEnabled = File.Exists(_lastRenderedFile);
MessageBox.Show(this, "The AmiReel render completed successfully.", "Render complete",
MessageBoxButton.OK, MessageBoxImage.Information);
}
@@ -140,6 +150,27 @@ public partial class MainWindow : Window
Process.Start(new ProcessStartInfo("explorer.exe", OutputFolderBox.Text) { UseShellExecute = true });
}
+ private void PreviewSource_Click(object sender, RoutedEventArgs e)
+ {
+ if (InputList.SelectedItem is string path)
+ OpenPreview(path);
+ }
+
+ private void PreviewRendered_Click(object sender, RoutedEventArgs e)
+ {
+ if (!string.IsNullOrWhiteSpace(_lastRenderedFile) && File.Exists(_lastRenderedFile))
+ OpenPreview(_lastRenderedFile);
+ }
+
+ private void InputList_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ => PreviewSourceButton.IsEnabled = InputList.SelectedItem is string;
+
+ private void InputList_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
+ {
+ if (InputList.SelectedItem is string path)
+ OpenPreview(path);
+ }
+
private void SetRendering(bool rendering)
{
RenderButton.IsEnabled = !rendering;
@@ -187,6 +218,7 @@ public partial class MainWindow : Window
EndCardBox.Text.Trim(),
FfmpegPathBox.Text.Trim(),
FfprobePathBox.Text.Trim(),
+ PreviewPlayerPathBox.Text.Trim(),
SelectedTheme(),
SelectedEncoder(),
TrimBox.Text.Trim(),
@@ -195,6 +227,42 @@ public partial class MainWindow : Window
IntervalBox.Text.Trim()));
}
+ private void OpenPreview(string mediaPath)
+ {
+ if (!File.Exists(mediaPath))
+ {
+ MessageBox.Show(this, "The selected media file was not found.", "Preview unavailable",
+ MessageBoxButton.OK, MessageBoxImage.Warning);
+ return;
+ }
+
+ string playerPath = PreviewPlayerPathBox.Text.Trim();
+
+ try
+ {
+ if (!string.IsNullOrWhiteSpace(playerPath))
+ {
+ if (!File.Exists(playerPath))
+ throw new FileNotFoundException("Configured preview player was not found.", playerPath);
+
+ ProcessStartInfo customPlayer = new()
+ {
+ FileName = playerPath,
+ UseShellExecute = false
+ };
+ customPlayer.ArgumentList.Add(mediaPath);
+ Process.Start(customPlayer);
+ return;
+ }
+
+ Process.Start(new ProcessStartInfo(mediaPath) { UseShellExecute = true });
+ }
+ catch (Exception exception)
+ {
+ MessageBox.Show(this, exception.Message, "Preview failed", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
private string SelectedTheme() => ((ComboBoxItem)ThemeBox.SelectedItem).Tag!.ToString()!;
private string SelectedEncoder() => ((ComboBoxItem)EncoderBox.SelectedItem).Tag!.ToString()!;
diff --git a/Models/AppSettings.cs b/Models/AppSettings.cs
index f123818..20b2c37 100644
--- a/Models/AppSettings.cs
+++ b/Models/AppSettings.cs
@@ -5,6 +5,7 @@ public sealed record AppSettings(
string EndCardPath,
string FfmpegPath,
string FfprobePath,
+ string PreviewPlayerPath,
string Theme,
string Encoder,
string TrimStart,
@@ -17,6 +18,7 @@ public sealed record AppSettings(
"",
"",
"",
+ "",
"Dark",
"Auto",
"4.414",
@@ -29,6 +31,7 @@ public sealed record AppSettings(
EndCardPath ?? "",
FfmpegPath ?? "",
FfprobePath ?? "",
+ PreviewPlayerPath ?? "",
string.IsNullOrWhiteSpace(Theme) ? "Dark" : Theme,
string.IsNullOrWhiteSpace(Encoder) ? "Auto" : Encoder,
string.IsNullOrWhiteSpace(TrimStart) ? "4.414" : TrimStart,
diff --git a/Services/ProcessRunner.cs b/Services/ProcessRunner.cs
index 40dce08..a9dd73c 100644
--- a/Services/ProcessRunner.cs
+++ b/Services/ProcessRunner.cs
@@ -92,17 +92,18 @@ public sealed partial class ProcessRunner
private static bool IsNoise(ProcessOutput output)
{
- if (output.Frame is not null || output.Time is not null)
- return true;
-
string line = output.Line.TrimStart();
if (string.IsNullOrWhiteSpace(line))
return true;
+ if (line.StartsWith("frame=", StringComparison.OrdinalIgnoreCase))
+ return true;
+
+ if (output.Frame is not null || output.Time is not null)
+ return true;
+
if (line.StartsWith("ERROR:", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("WARNING:", StringComparison.OrdinalIgnoreCase)
- || line.Contains("selected.", StringComparison.OrdinalIgnoreCase)
- || line.Contains("Falling back to FFmpeg from PATH", StringComparison.OrdinalIgnoreCase)
|| line.Contains("skipped", StringComparison.OrdinalIgnoreCase)
|| line.Contains("could not", StringComparison.OrdinalIgnoreCase)
|| line.Contains("failed", StringComparison.OrdinalIgnoreCase)
@@ -128,6 +129,7 @@ public sealed partial class ProcessRunner
|| line.StartsWith("Side data:", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("encoder :", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("title :", StringComparison.OrdinalIgnoreCase)
+ || line.StartsWith("CPB properties:", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("Press [q] to stop", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("[", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("Duration:", StringComparison.OrdinalIgnoreCase)
diff --git a/Services/RenderPipeline.cs b/Services/RenderPipeline.cs
index 2b08ab2..e4bb9ee 100644
--- a/Services/RenderPipeline.cs
+++ b/Services/RenderPipeline.cs
@@ -48,7 +48,7 @@ public sealed class RenderPipeline
"-r", settings.FramesPerSecond.ToString(CultureInfo.InvariantCulture)];
joinArgs.AddRange(EncoderArguments(encoder));
joinArgs.AddRange(["-c:a", "aac", "-b:a", "384k", "-ar", "48000", main]);
- await RunStageAsync(tools.Ffmpeg, joinArgs, log, progress, "Joining recordings", 2, 48, joinDuration, token);
+ await RunStageAsync(tools.Ffmpeg, joinArgs, log, progress, "Joining recordings", 2, 48, joinDuration, settings.FramesPerSecond, token);
double originalDuration = await probe.ProbeDurationAsync(tools, main, token);
double trimmedDuration = originalDuration - settings.TrimStart;
@@ -104,7 +104,7 @@ public sealed class RenderPipeline
"-map", "[v]", "-map", "[a]", "-r", settings.FramesPerSecond.ToString()]);
finalArgs.AddRange(EncoderArguments(encoder));
finalArgs.AddRange(["-c:a", "aac", "-b:a", "384k", "-ar", "48000", "-movflags", "+faststart", final]);
- await RunStageAsync(tools.Ffmpeg, finalArgs, log, progress, "Final render", 60, 99, finalDuration, token);
+ await RunStageAsync(tools.Ffmpeg, finalArgs, log, progress, "Final render", 60, 99, finalDuration, settings.FramesPerSecond, token);
progress.Report(new(100, "Complete", final));
}
finally
@@ -120,7 +120,7 @@ public sealed class RenderPipeline
try
{
await _runner.RunAsync(tools.Ffmpeg,
- ["-hide_banner", "-loglevel", "error", "-f", "lavfi", "-i", "color=c=black:s=128x128:r=1",
+ ["-hide_banner", "-loglevel", "error", "-f", "lavfi", "-i", "color=c=black:s=640x360:r=1",
"-frames:v", "1", "-an", "-c:v", "h264_nvenc", "-preset", "p6", "-f", "null", "-"],
null, null, token);
nvenc = true;
@@ -129,10 +129,9 @@ public sealed class RenderPipeline
{
nvenc = false;
}
- if (nvenc) { log("NVIDIA NVENC selected."); return "nvenc"; }
+ if (nvenc) return "nvenc";
if (requested == EncoderMode.NvidiaNvenc)
- throw new InvalidOperationException("NVIDIA NVENC was requested but could not initialize.");
- log("NVENC unavailable; CPU libx264 selected.");
+ log("WARNING: NVIDIA NVENC was requested but could not initialize. Falling back to CPU libx264.");
return "x264";
}
@@ -175,7 +174,6 @@ public sealed class RenderPipeline
"AmiReel found FFprobe on PATH, but it could not be started.",
token);
- log($"Configured or embedded FFmpeg tools are unusable. Falling back to FFmpeg from PATH: '{systemTools.Ffmpeg}' and '{systemTools.Ffprobe}'.");
return systemTools;
}
}
@@ -235,7 +233,7 @@ public sealed class RenderPipeline
}
private async Task RunStageAsync(string executable, IEnumerable args, Action log,
- IProgress progress, string stageName, double from, double to, double? duration, CancellationToken token)
+ IProgress progress, string stageName, double from, double to, double? duration, int framesPerSecond, CancellationToken token)
{
await _runner.RunAsync(executable, args, log, null, token, outputHandler: output =>
{
@@ -243,22 +241,23 @@ public sealed class RenderPipeline
return;
double percent = from + Math.Min(1, time.TotalSeconds / duration.Value) * (to - from);
- string message = BuildProgressMessage(time, duration.Value, output.FramesPerSecond, output.Speed, output.Frame);
+ string message = BuildProgressMessage(time, duration.Value, output.FramesPerSecond, output.Speed, output.Frame, framesPerSecond);
progress.Report(new(percent, stageName, message));
});
}
- private static string BuildProgressMessage(TimeSpan current, double durationSeconds, double? fps, double? speed, int? frame)
+ private static string BuildProgressMessage(TimeSpan current, double durationSeconds, double? fps, double? speed, int? frame, int framesPerSecond)
{
TimeSpan total = TimeSpan.FromSeconds(durationSeconds);
List parts = [$"{current:hh\\:mm\\:ss} / {total:hh\\:mm\\:ss}"];
+ int totalFrames = Math.Max(1, (int)Math.Ceiling(durationSeconds * framesPerSecond));
if (fps is > 0)
parts.Add($"{fps:0.#} fps");
if (speed is > 0)
parts.Add($"{speed:0.##}x");
if (frame is > 0)
- parts.Add($"frame {frame.Value:N0}");
+ parts.Add($"frame {frame.Value:N0} / {totalFrames:N0}");
return string.Join(" · ", parts);
}
diff --git a/Services/ToolExtractor.cs b/Services/ToolExtractor.cs
index d77c4a7..075cb17 100644
--- a/Services/ToolExtractor.cs
+++ b/Services/ToolExtractor.cs
@@ -19,6 +19,9 @@ public static class ToolExtractor
return new ToolPaths(ffmpegPath, ffprobePath);
}
+ if (TryResolveFromSystemPath(out ToolPaths? systemTools) && systemTools is not null)
+ return systemTools;
+
return await ExtractAsync(cancellationToken);
}