From fb12b61e8d357dbcd9958771c1d4e2aa0a831c11 Mon Sep 17 00:00:00 2001 From: Steven Pelech Date: Sun, 20 Sep 2026 22:00:16 -0500 Subject: [PATCH 1/7] docs: add design spec for Avalonia.LayoutInspector NuGet package --- ...-09-20-avalonia-layout-inspector-design.md | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-20-avalonia-layout-inspector-design.md diff --git a/docs/superpowers/specs/2026-09-20-avalonia-layout-inspector-design.md b/docs/superpowers/specs/2026-09-20-avalonia-layout-inspector-design.md new file mode 100644 index 0000000..2e04283 --- /dev/null +++ b/docs/superpowers/specs/2026-09-20-avalonia-layout-inspector-design.md @@ -0,0 +1,180 @@ +# Avalonia.LayoutInspector — Design Specification + +- **Date:** 2026-09-20 +- **Topic:** Avalonia Layout Inspector C# NuGet Package & Test Harness +- **Status:** Approved +- **Repository Location:** `C:\Users\Alias\repos\Avalonia.LayoutInspector` + +--- + +## 1. Executive Summary + +This specification establishes the architecture, rule pipeline, test harness, and packaging for **`Avalonia.LayoutInspector`**, an open-source C# library and NuGet package for Avalonia UI. It provides automated geometric layout auditing, collision detection, boundary overflow analysis, touch target ergonomics inspection, and responsive viewport sweeping across Desktop and Mobile form factors. + +It replaces web-only Playwright DOM inspectors for Avalonia applications by directly analyzing Avalonia's native `VisualTree` and geometric bounds in both headless automated test suites and interactive runtime environments. + +--- + +## 2. Architecture & System Topology + +```mermaid +flowchart TD + subgraph TestRunner ["Test Runner (xUnit / NUnit / MSTest)"] + A["Test Method"] -->|Assert| B["Fluent Assertions / Extension Methods"] + end + + subgraph CoreEngine ["Avalonia.LayoutInspector Core Engine"] + B --> C["LayoutAuditor"] + C --> D["VisualBoundsResolver"] + D -->|Compute Root Coordinates| E["Visual Tree Traversal"] + + subgraph RulesPipeline ["Inspection Rules Pipeline"] + F1["BoundaryOverflowRule"] + F2["SiblingCollisionRule"] + F3["TargetErgonomicsRule"] + F4["TextClippingRule"] + end + + E --> RulesPipeline + RulesPipeline --> G["AuditReport & Scoring Engine"] + end + + subgraph ResponsiveHarness ["Responsive Sweeper"] + H["ResponsiveAuditRunner"] -->|Resize Viewport| C + H -->|Standard Breakpoints: 1080p, 720p, iPad, Mobile| G + end + + subgraph LiveApp ["Interactive Runtime Debugger"] + I["LayoutInspectorOverlay"] -->|Attach to TopLevel| C + I -->|Render HUD & Outlines| J["Adorner / Visual Feedback"] + end +``` + +--- + +## 3. Component Specifications + +### 3.1 VisualBoundsResolver & Geometry Engine +- **Coordinate Space Transformation**: Transforms element local `Bounds` to root window coordinates using `visual.TransformToVisual(rootVisual)` and `visual.TranslatePoint(new Point(0, 0), rootVisual)`. +- **Layout Pass Guarantee**: Ensures controls are measured and arranged prior to inspection via `control.UpdateLayout()` if bounds are uninitialized (`Width == 0 && Height == 0`). +- **Visibility Filtering**: Excludes collapsed or invisible visuals (`IsVisible == false`, `Opacity <= 0`, or width/height of zero). + +### 3.2 Rules Pipeline (`ILayoutAuditRule`) + +```csharp +public interface ILayoutAuditRule +{ + string RuleId { get; } + string Name { get; } + IEnumerable Evaluate(Visual root, VisualBoundsResolver resolver, AuditOptions options); +} +``` + +1. **`BoundaryOverflowRule` (Rule ID: `LAYOUT001_OVERFLOW`)**: + - Detects child visual bounds extending beyond container or viewport boundaries. + - Respects `ScrollViewer` scrollable axes (`HorizontalScrollBarVisibility` / `VerticalScrollBarVisibility`). + - Respects controls with `ClipToBounds = true`. + - Detects negative coordinate shifting (`X < 0` or `Y < 0`). +2. **`SiblingCollisionRule` (Rule ID: `LAYOUT002_COLLISION`)**: + - Calculates 2D Axis-Aligned Bounding Box (AABB) intersections between sibling elements. + - Automatically excludes layered containers (`Canvas`, unindexed `Panel`, controls with explicit `ZIndex`). + - Evaluates `Grid` layout coordinates: checks siblings only when sharing identical `Grid.Row` and `Grid.Column` without spanning separation. + - Applies 1px border tolerance to prevent false positives on touching edges. +3. **`TargetErgonomicsRule` (Rule ID: `LAYOUT003_ERGONOMICS`)**: + - Identifies interactive controls (`Button`, `ToggleButton`, `TextBox`, `ComboBox`, `CheckBox`, `RadioButton`, `Slider`, `MenuItem`, and custom pointer interactors). + - Validates dimensions against `MinTouchTargetSize` (default 24.0px). + - Audits adjacent target spacing (flags targets spaced $< 8.0\text{px}$ apart). +4. **`TextClippingRule` (Rule ID: `LAYOUT004_TRUNCATION`)**: + - Audits `TextBlock` and `SelectableTextBlock` controls. + - Flags unhandled truncation where `DesiredSize.Width > Bounds.Width` and `TextWrapping == NoWrap` with `TextTrimming == None`. + - Flags collapsed labels whose rendered height is below the font line height. + +### 3.3 UX Health Scoring Engine +- Base: 100 points. +- Deductions: -15 per Boundary Overflow, -15 per Sibling Collision, -5 per Ergonomics violation, -5 per Text clipping. +- Grade brackets: + - **A**: 90–100 (Clean, production ready) + - **B**: 80–89 (Minor ergonomics or text trimming warnings) + - **C**: 70–79 (Substantial layout issues) + - **F**: $< 70$ (Critical layout overflows or collisions) + +--- + +## 4. Responsive Sweeper & Assertion API + +### 4.1 Breakpoints +Provides predefined standard breakpoints: +- `Desktop1440p` (2560 x 1440) +- `Desktop1080p` (1920 x 1080) +- `Desktop720p` (1280 x 720) +- `TabletiPad` (768 x 1024) +- `MobilePortrait` (412 x 915) + +### 4.2 Fluent Assertions +Extension methods providing seamless integration with xUnit, NUnit, and MSTest: +```csharp +public static class LayoutAssertExtensions +{ + public static AuditReport ShouldHaveNoLayoutViolations(this Visual visual, AuditOptions? options = null); + public static AuditReport ShouldHaveNoOverflow(this Visual visual); + public static AuditReport ShouldHaveNoCollisions(this Visual visual); + public static AuditReport ShouldHaveTouchFriendlyTargets(this Visual visual, double minSize = 24.0); + public static ResponsiveAuditReport ShouldFitResponsiveBreakpoints( + this Window window, + IEnumerable? breakpoints = null, + AuditOptions? options = null); +} +``` + +--- + +## 5. Repository Structure & Toolbelt Standards + +Repository root: `C:\Users\Alias\repos\Avalonia.LayoutInspector` + +```text +Avalonia.LayoutInspector/ +├── .github/ +│ └── workflows/ +│ └── ci.yml +├── docs/ +│ ├── ARCHITECTURE.md +│ └── TEST_CATALOG.md +├── src/ +│ └── Avalonia.LayoutInspector/ +│ ├── Assertions/ +│ ├── Diagnostics/ +│ ├── Engine/ +│ ├── Models/ +│ ├── Overlay/ +│ ├── Rules/ +│ └── Avalonia.LayoutInspector.csproj +├── tests/ +│ └── Avalonia.LayoutInspector.Tests/ +│ ├── Fixtures/ +│ ├── Rules/ +│ ├── Runners/ +│ └── Avalonia.LayoutInspector.Tests.csproj +├── Directory.Build.props +├── Avalonia.LayoutInspector.slnx +├── AGENTS.md +├── CHANGELOG.md +├── LICENSE +└── README.md +``` + +### 5.1 Standards Compliance +- **Target Frameworks**: Multi-targets `net8.0;net9.0;net10.0` for core library; `net10.0` for test project. +- **Modern Solution**: `.slnx` solution file. +- **Code Style**: `enable`, `enable`, client-focused interfaces (`ILayoutAuditor`, `ILayoutAuditRule`, `IVisualBoundsResolver`). +- **Tests**: $\ge 80\%$ test coverage using `Avalonia.Headless.XUnit`. +- **NuGet Packaging**: Generates `Avalonia.LayoutInspector.nupkg` with symbol package (`.snupkg`), XML documentation, and license embedded. + +--- + +## 6. Verification & Quality Gates + +1. **Compilation**: `dotnet build` passes with zero warnings and zero errors across all target frameworks (`net8.0`, `net9.0`, `net10.0`). +2. **Unit Tests**: Full test suite in `Avalonia.LayoutInspector.Tests` executes and passes via `dotnet test`. +3. **Packaging**: `dotnet pack -c Release` produces valid NuGet packages ready for distribution. +4. **Integration Verification**: Consume the package in `LocalLLMServerManager.Tests` to run layout audits against `MainWindow`, `CivitaiTabControl`, `HuggingFaceTabControl`, `OllamaModelsTabControl`, and `SettingsTabControl`. From a1641673ec3307e1dbccb4277023a9ccf5b0cf10 Mon Sep 17 00:00:00 2001 From: Steven Pelech Date: Sun, 20 Sep 2026 22:02:12 -0500 Subject: [PATCH 2/7] docs: add implementation plan for Avalonia.LayoutInspector --- .../2026-09-20-avalonia-layout-inspector.md | 463 ++++++++++++++++++ 1 file changed, 463 insertions(+) create mode 100644 docs/superpowers/plans/2026-09-20-avalonia-layout-inspector.md diff --git a/docs/superpowers/plans/2026-09-20-avalonia-layout-inspector.md b/docs/superpowers/plans/2026-09-20-avalonia-layout-inspector.md new file mode 100644 index 0000000..d6b928e --- /dev/null +++ b/docs/superpowers/plans/2026-09-20-avalonia-layout-inspector.md @@ -0,0 +1,463 @@ +# Avalonia.LayoutInspector Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Create `Avalonia.LayoutInspector`, a standalone C# library and NuGet package for automated layout inspection, boundary overflow detection, sibling collision auditing, touch target ergonomics validation, and responsive viewport sweeping, and integrate it into `LocalLLMServerManager` while deprecating the old WASM playwright inspector. + +**Architecture:** +1. Standalone multi-target library (`net8.0;net9.0;net10.0`) in `C:\Users\Alias\repos\Avalonia.LayoutInspector` built with modern `.slnx` and `Directory.Build.props`. +2. Core geometric coordinate engine (`VisualBoundsResolver`) mapping Avalonia visual bounds into root coordinate space. +3. Modular rules pipeline (`BoundaryOverflowRule`, `SiblingCollisionRule`, `TargetErgonomicsRule`, `TextClippingRule`) and 0-100 UX scoring engine. +4. Responsive viewport sweeper (`ResponsiveAuditRunner`) and fluent assertions (`ShouldHaveNoLayoutViolations`, `ShouldFitResponsiveBreakpoints`). +5. Live diagnostic adorner overlay (`LayoutInspectorOverlay`) and headless test suite with $\ge 80\%$ test coverage. +6. Integration into `LocalLLMServerManager.Tests` to identify UI layout issues and removal of `playwright-layout-inspector`. + +**Tech Stack:** C# 13 / .NET 10 (multi-targeting net8.0, net9.0, net10.0), Avalonia UI 11.2, Avalonia.Headless.XUnit, xUnit. + +## Global Constraints + +- Repository target directory: `C:\Users\Alias\repos\Avalonia.LayoutInspector`. +- Core library must multi-target `net8.0;net9.0;net10.0`. +- Nullable reference types enabled (`enable`) and warnings as errors. +- Test coverage $\ge 80\%$ verified via `dotnet test`. +- All rules follow ASD-STE100 principles for reporting and diagnostics. +- Follow AgenticEngineeringToolbelt repository standards (AGENTS.md, Git Flow, atomic commits). + +--- + +### Task 1: Scaffold `Avalonia.LayoutInspector` Repository & Solution + +**Files:** +- Create: `C:\Users\Alias\repos\Avalonia.LayoutInspector/Directory.Build.props` +- Create: `C:\Users\Alias\repos\Avalonia.LayoutInspector/Avalonia.LayoutInspector.slnx` +- Create: `C:\Users\Alias\repos\Avalonia.LayoutInspector/AGENTS.md` +- Create: `C:\Users\Alias\repos\Avalonia.LayoutInspector/README.md` +- Create: `C:\Users\Alias\repos\Avalonia.LayoutInspector/LICENSE` +- Create: `C:\Users\Alias\repos\Avalonia.LayoutInspector/src/Avalonia.LayoutInspector/Avalonia.LayoutInspector.csproj` +- Create: `C:\Users\Alias\repos\Avalonia.LayoutInspector/tests/Avalonia.LayoutInspector.Tests/Avalonia.LayoutInspector.Tests.csproj` + +**Interfaces:** +- Produces: Compiled multi-target solution ready for engine classes and test execution. + +- [ ] **Step 1: Create repository directory and Directory.Build.props** + +```xml + + + enable + enable + 13.0 + true + 1.0.0 + Steven T. Pelech + MIT + https://github.com/spelech/Avalonia.LayoutInspector + https://github.com/spelech/Avalonia.LayoutInspector + avalonia;layout;inspector;ui-testing;headless;ergonomics;responsive + Automated layout auditing, overflow detection, sibling collision checking, and responsive viewport inspection for Avalonia UI. + + +``` + +- [ ] **Step 2: Create Avalonia.LayoutInspector.csproj** + +```xml + + + net8.0;net9.0;net10.0 + true + false + true + snupkg + + + + + + +``` + +- [ ] **Step 3: Create Avalonia.LayoutInspector.Tests.csproj and TestAppBuilder** + +```xml + + + net10.0 + false + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + +``` + +- [ ] **Step 4: Create solution and smoke test project build** + +Run: `dotnet build Avalonia.LayoutInspector.slnx` +Expected: Build succeeded with 0 warnings and 0 errors. + +- [ ] **Step 5: Initialize Git repository and commit baseline** + +```bash +git init +git checkout -b main +git add . +git commit -m "chore: initial scaffold of Avalonia.LayoutInspector solution" +``` + +--- + +### Task 2: Core Models, Interfaces & VisualBoundsResolver + +**Files:** +- Create: `src/Avalonia.LayoutInspector/Models/ViolationSeverity.cs` +- Create: `src/Avalonia.LayoutInspector/Models/LayoutViolation.cs` +- Create: `src/Avalonia.LayoutInspector/Models/AuditReport.cs` +- Create: `src/Avalonia.LayoutInspector/Models/AuditOptions.cs` +- Create: `src/Avalonia.LayoutInspector/Rules/ILayoutAuditRule.cs` +- Create: `src/Avalonia.LayoutInspector/Engine/IVisualBoundsResolver.cs` +- Create: `src/Avalonia.LayoutInspector/Engine/VisualBoundsResolver.cs` +- Test: `tests/Avalonia.LayoutInspector.Tests/Engine/VisualBoundsResolverTests.cs` + +**Interfaces:** +- Produces: `VisualBoundsResolver` mapping any visual to root coordinates with layout pass execution and visibility filtering. + +- [ ] **Step 1: Write failing unit test for VisualBoundsResolver** + +```csharp +[AvaloniaFact] +public void ResolveBounds_NestedControl_ReturnsExpectedRootCoordinates() +{ + var resolver = new VisualBoundsResolver(); + var innerButton = new Button { Width = 100, Height = 40 }; + var container = new Border { Margin = new Thickness(20), Child = innerButton }; + var window = new Window { Content = container, Width = 400, Height = 300 }; + window.Show(); + + var bounds = resolver.GetRootBounds(innerButton, window); + Assert.True(bounds.HasValue); + Assert.Equal(20, bounds.Value.X); + Assert.Equal(20, bounds.Value.Y); + Assert.Equal(100, bounds.Value.Width); + Assert.Equal(40, bounds.Value.Height); + window.Close(); +} +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `dotnet test --filter "FullyQualifiedName~VisualBoundsResolverTests"` +Expected: FAIL (types not found) + +- [ ] **Step 3: Implement VisualBoundsResolver and models** + +```csharp +public class VisualBoundsResolver : IVisualBoundsResolver +{ + public Rect? GetRootBounds(Visual visual, Visual root) + { + if (!visual.IsVisible) return null; + if (visual.Bounds.Width <= 0 || visual.Bounds.Height <= 0) + { + if (visual is Layoutable layoutable) layoutable.UpdateLayout(); + } + var transform = visual.TransformToVisual(root); + if (!transform.HasValue) return null; + return new Rect(0, 0, visual.Bounds.Width, visual.Bounds.Height).TransformToAABB(transform.Value); + } +} +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `dotnet test --filter "FullyQualifiedName~VisualBoundsResolverTests"` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add src/ tests/ +git commit -m "feat(engine): implement VisualBoundsResolver and core layout models" +``` + +--- + +### Task 3: Implement Rules Pipeline + +**Files:** +- Create: `src/Avalonia.LayoutInspector/Rules/BoundaryOverflowRule.cs` +- Create: `src/Avalonia.LayoutInspector/Rules/SiblingCollisionRule.cs` +- Create: `src/Avalonia.LayoutInspector/Rules/TargetErgonomicsRule.cs` +- Create: `src/Avalonia.LayoutInspector/Rules/TextClippingRule.cs` +- Test: `tests/Avalonia.LayoutInspector.Tests/Rules/BoundaryOverflowRuleTests.cs` +- Test: `tests/Avalonia.LayoutInspector.Tests/Rules/SiblingCollisionRuleTests.cs` +- Test: `tests/Avalonia.LayoutInspector.Tests/Rules/TargetErgonomicsRuleTests.cs` +- Test: `tests/Avalonia.LayoutInspector.Tests/Rules/TextClippingRuleTests.cs` + +**Interfaces:** +- Produces: Specialized `ILayoutAuditRule` implementations detecting overflow, collisions, target size violations, and text clipping. + +- [ ] **Step 1: Write failing tests for BoundaryOverflowRule and SiblingCollisionRule** + +```csharp +[AvaloniaFact] +public void BoundaryOverflowRule_ChildBleedingBeyondContainer_DetectsViolation() +{ + var child = new Border { Width = 500, Height = 100 }; + var parent = new Border { Width = 300, Height = 100, Child = child }; + var window = new Window { Content = parent, Width = 600, Height = 400 }; + window.Show(); + + var rule = new BoundaryOverflowRule(); + var violations = rule.Evaluate(window, new VisualBoundsResolver(), new AuditOptions()).ToList(); + Assert.Contains(violations, v => v.RuleId == "LAYOUT001_OVERFLOW"); + window.Close(); +} +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `dotnet test --filter "FullyQualifiedName~BoundaryOverflowRuleTests"` +Expected: FAIL + +- [ ] **Step 3: Implement BoundaryOverflowRule, SiblingCollisionRule, TargetErgonomicsRule, TextClippingRule** + +Implement container bounds checking, ScrollViewer exemptions, Grid row/column matching for collisions, interactive element hitbox auditing, and text trimming detection. + +- [ ] **Step 4: Run all rule tests to verify they pass** + +Run: `dotnet test --filter "FullyQualifiedName~RuleTests"` +Expected: All tests PASS + +- [ ] **Step 5: Commit** + +```bash +git add src/ tests/ +git commit -m "feat(rules): implement boundary overflow, sibling collision, ergonomics, and text clipping rules" +``` + +--- + +### Task 4: Implement LayoutAuditor & Scoring Engine + +**Files:** +- Create: `src/Avalonia.LayoutInspector/Engine/ILayoutAuditor.cs` +- Create: `src/Avalonia.LayoutInspector/Engine/LayoutAuditor.cs` +- Create: `src/Avalonia.LayoutInspector/Engine/LayoutScoreCalculator.cs` +- Test: `tests/Avalonia.LayoutInspector.Tests/Engine/LayoutAuditorTests.cs` + +**Interfaces:** +- Produces: `LayoutAuditor.Audit(visual, options)` returning an `AuditReport` with detailed violations, recommendations, and 0-100 UX score. + +- [ ] **Step 1: Write failing test for LayoutAuditor end-to-end audit** + +```csharp +[AvaloniaFact] +public void LayoutAuditor_CleanView_Returns100ScoreAndZeroViolations() +{ + var panel = new StackPanel + { + Children = + { + new Button { Content = "Save", Width = 100, Height = 32 }, + new Button { Content = "Cancel", Width = 100, Height = 32 } + } + }; + var window = new Window { Content = panel, Width = 400, Height = 300 }; + window.Show(); + + var auditor = new LayoutAuditor(); + var report = auditor.Audit(window); + Assert.True(report.IsClean); + Assert.Equal(100, report.HealthScore); + window.Close(); +} +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `dotnet test --filter "FullyQualifiedName~LayoutAuditorTests"` +Expected: FAIL + +- [ ] **Step 3: Implement LayoutAuditor and LayoutScoreCalculator** + +Wire up the default rule pipeline, tree traversal, violation aggregation, and markdown table summary formatting (`ToDetailedReport()`). + +- [ ] **Step 4: Run test to verify it passes** + +Run: `dotnet test --filter "FullyQualifiedName~LayoutAuditorTests"` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add src/ tests/ +git commit -m "feat(engine): implement LayoutAuditor and layout health scoring engine" +``` + +--- + +### Task 5: Implement Responsive Sweeper & Fluent Assertions + +**Files:** +- Create: `src/Avalonia.LayoutInspector/Responsive/Breakpoint.cs` +- Create: `src/Avalonia.LayoutInspector/Responsive/StandardBreakpoints.cs` +- Create: `src/Avalonia.LayoutInspector/Responsive/ResponsiveAuditRunner.cs` +- Create: `src/Avalonia.LayoutInspector/Responsive/ResponsiveAuditReport.cs` +- Create: `src/Avalonia.LayoutInspector/Assertions/LayoutAssertExtensions.cs` +- Test: `tests/Avalonia.LayoutInspector.Tests/Responsive/ResponsiveAuditRunnerTests.cs` +- Test: `tests/Avalonia.LayoutInspector.Tests/Assertions/LayoutAssertExtensionsTests.cs` + +**Interfaces:** +- Produces: `ShouldHaveNoLayoutViolations()`, `ShouldFitResponsiveBreakpoints()` extension methods and multi-breakpoint sweeper. + +- [ ] **Step 1: Write failing test for responsive sweeper** + +```csharp +[AvaloniaFact] +public void ShouldFitResponsiveBreakpoints_ResponsiveView_ExecutesAllBreakpoints() +{ + var window = new Window + { + Content = new Grid { Width = 300, Height = 200 } + }; + var report = window.ShouldFitResponsiveBreakpoints(new[] + { + new Breakpoint("1080p", 1920, 1080), + new Breakpoint("Mobile", 412, 915) + }); + Assert.Equal(2, report.BreakpointResults.Count); + Assert.True(report.AllPassed); +} +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `dotnet test --filter "FullyQualifiedName~ResponsiveAuditRunnerTests"` +Expected: FAIL + +- [ ] **Step 3: Implement ResponsiveAuditRunner and LayoutAssertExtensions** + +Implement breakpoint sweeping, window resizing, layout pass execution, and fluent assertion exceptions. + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `dotnet test --filter "FullyQualifiedName~ResponsiveAuditRunnerTests"` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add src/ tests/ +git commit -m "feat(responsive): implement responsive viewport sweeper and fluent test assertions" +``` + +--- + +### Task 6: Implement In-App Diagnostic Overlay & HUD + +**Files:** +- Create: `src/Avalonia.LayoutInspector/Overlay/LayoutInspectorOverlay.cs` +- Test: `tests/Avalonia.LayoutInspector.Tests/Overlay/LayoutInspectorOverlayTests.cs` + +**Interfaces:** +- Produces: Lightweight adorner overlay drawing colored bounding box outlines for violations and on-screen HUD badge. + +- [ ] **Step 1: Write failing test for LayoutInspectorOverlay** + +```csharp +[AvaloniaFact] +public void LayoutInspectorOverlay_AttachToTopLevel_RendersWithoutErrors() +{ + var window = new Window { Width = 800, Height = 600 }; + window.Show(); + var overlay = LayoutInspectorOverlay.Attach(window); + Assert.NotNull(overlay); + window.Close(); +} +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `dotnet test --filter "FullyQualifiedName~LayoutInspectorOverlayTests"` +Expected: FAIL + +- [ ] **Step 3: Implement LayoutInspectorOverlay** + +Build custom adorner canvas drawing collision boxes in red, overflow boxes in dashed orange, ergonomics in yellow, and HUD badge. + +- [ ] **Step 4: Run test to verify it passes** + +Run: `dotnet test --filter "FullyQualifiedName~LayoutInspectorOverlayTests"` +Expected: PASS + +- [ ] **Step 5: Run full test suite and pack NuGet package** + +Run: `dotnet test` +Run: `dotnet pack -c Release` +Expected: 100% tests pass, `Avalonia.LayoutInspector.1.0.0.nupkg` created. + +- [ ] **Step 6: Commit** + +```bash +git add src/ tests/ +git commit -m "feat(overlay): implement in-app visual diagnostic overlay and pack release" +``` + +--- + +### Task 7: Integrate into LocalLLMServerManager & Clean Up Deprecated Tooling + +**Files:** +- Modify: `C:\Users\Alias\repos\LocalLLMServerManager/package.json` (remove `playwright-layout-inspector` and `test:layout`) +- Delete: `C:\Users\Alias\repos\LocalLLMServerManager/playwright.config.ts` +- Delete: `C:\Users\Alias\repos\LocalLLMServerManager/tests/layout-inspector/` +- Modify: `C:\Users\Alias\repos\LocalLLMServerManager/LocalLLMServerManager.Tests/LocalLLMServerManager.Tests.csproj` (reference `Avalonia.LayoutInspector`) +- Create: `C:\Users\Alias\repos\LocalLLMServerManager/LocalLLMServerManager.Tests/AvaloniaLayoutAuditTests.cs` + +**Interfaces:** +- Consumes: `Avalonia.LayoutInspector` to run native layout audits across `LocalLLMServerManager` controls and detect existing UI bugs. + +- [ ] **Step 1: Remove deprecated playwright-layout-inspector from package.json** + +Remove `playwright-layout-inspector` dependency and `test:layout` script. Delete `playwright.config.ts` and `tests/layout-inspector/`. + +- [ ] **Step 2: Run npm lint and typecheck** + +Run: `npm run lint` and `npx tsc --noEmit` +Expected: PASS + +- [ ] **Step 3: Add reference to Avalonia.LayoutInspector in LocalLLMServerManager.Tests.csproj** + +```xml + +``` + +- [ ] **Step 4: Create AvaloniaLayoutAuditTests.cs** + +Audit `MainWindow`, `CivitaiTabControl`, `HuggingFaceTabControl`, `OllamaModelsTabControl`, and `SettingsTabControl` across `StandardBreakpoints.AllStandard`. + +- [ ] **Step 5: Run layout audit tests to capture UI issues** + +Run: `dotnet test --filter "FullyQualifiedName~AvaloniaLayoutAuditTests"` +Record any detected layout overlaps, overflows, or clipping issues for diagnosis. + +- [ ] **Step 6: Commit LocalLLMServerManager updates** + +```bash +git add package.json LocalLLMServerManager.Tests/ +git commit -m "chore(tooling): replace playwright-layout-inspector with native Avalonia.LayoutInspector test suite" +``` From ba47f395fe061956115c190bc00ef8df811b1cd2 Mon Sep 17 00:00:00 2001 From: Steven Pelech Date: Sun, 20 Sep 2026 22:45:35 -0500 Subject: [PATCH 3/7] chore(tooling): replace playwright-layout-inspector with native Avalonia.LayoutInspector test suite --- .../AvaloniaLayoutAuditTests.cs | 167 ++++++++++++++++++ .../LocalLLMServerManager.Tests.csproj | 1 + package.json | 4 +- playwright.config.ts | 44 ----- tests/layout-inspector/declarations.d.ts | 11 -- tests/layout-inspector/layout-audit.spec.ts | 21 --- tests/layout-inspector/matchers.d.ts | 12 -- 7 files changed, 169 insertions(+), 91 deletions(-) create mode 100644 LocalLLMServerManager.Tests/AvaloniaLayoutAuditTests.cs delete mode 100644 playwright.config.ts delete mode 100644 tests/layout-inspector/declarations.d.ts delete mode 100644 tests/layout-inspector/layout-audit.spec.ts delete mode 100644 tests/layout-inspector/matchers.d.ts diff --git a/LocalLLMServerManager.Tests/AvaloniaLayoutAuditTests.cs b/LocalLLMServerManager.Tests/AvaloniaLayoutAuditTests.cs new file mode 100644 index 0000000..04dfba5 --- /dev/null +++ b/LocalLLMServerManager.Tests/AvaloniaLayoutAuditTests.cs @@ -0,0 +1,167 @@ +using System; +using System.Linq; +using Avalonia.Controls; +using Avalonia.Headless.XUnit; +using Avalonia.LayoutInspector.Assertions; +using Avalonia.LayoutInspector.Engine; +using Avalonia.LayoutInspector.Models; +using Avalonia.LayoutInspector.Responsive; +using LocalLLMServerManager.Shared.ViewModels; +using LocalLLMServerManager.Shared.Views.Controls; +using LocalLLMServerManager.Views; +using Xunit; + +namespace LocalLLMServerManager.Tests; + +public class AvaloniaLayoutAuditTests +{ + private readonly ITestOutputHelper _output; + + public AvaloniaLayoutAuditTests(ITestOutputHelper output) + { + _output = output; + } + + [AvaloniaFact] + public void MainWindow_ResponsiveLayoutAudit() + { + var window = new MainWindow(); + try + { + var runner = new ResponsiveAuditRunner(); + var responsiveReport = runner.Run(window, StandardBreakpoints.AllStandard); + + _output.WriteLine($"MainWindow Responsive Audit - AllPassed: {responsiveReport.AllPassed}, TotalViolations: {responsiveReport.TotalViolations}"); + foreach (var (bp, report) in responsiveReport.BreakpointReports) + { + _output.WriteLine($"--- Breakpoint {bp.Name} ({bp.Width}x{bp.Height}): Health={report.HealthScore}/100, Violations={report.Violations.Count} ---"); + if (report.Violations.Count > 0) + { + _output.WriteLine(report.ToDetailedReport()); + } + } + + Assert.NotNull(responsiveReport); + Assert.NotEmpty(responsiveReport.BreakpointReports); + } + finally + { + window.Close(); + } + } + + [AvaloniaFact] + public void CivitaiTabControl_LayoutAudit() + { + var vm = new MainViewModel(); + var control = new CivitaiTabControl { DataContext = vm.Civitai }; + var window = new Window { Content = control, Width = 1280, Height = 800 }; + try + { + window.Show(); + + var auditor = new LayoutAuditor(); + var options = new AuditOptions + { + CheckBoundaryOverflow = true, + CheckSiblingCollisions = true, + CheckTouchErgonomics = false, + CheckTextClipping = false + }; + + var report = auditor.Audit(control, options); + _output.WriteLine($"CivitaiTabControl Audit - Health={report.HealthScore}/100, Violations={report.Violations.Count}"); + if (report.Violations.Count > 0) + { + _output.WriteLine(report.ToDetailedReport()); + } + + Assert.NotNull(report); + } + finally + { + window.Close(); + } + } + + [AvaloniaFact] + public void HuggingFaceTabControl_LayoutAudit() + { + var vm = new MainViewModel(); + var control = new HuggingFaceTabControl { DataContext = vm.HuggingFace }; + var window = new Window { Content = control, Width = 1280, Height = 800 }; + try + { + window.Show(); + + var auditor = new LayoutAuditor(); + var report = auditor.Audit(control); + + _output.WriteLine($"HuggingFaceTabControl Audit - Health={report.HealthScore}/100, Violations={report.Violations.Count}"); + if (report.Violations.Count > 0) + { + _output.WriteLine(report.ToDetailedReport()); + } + + Assert.NotNull(report); + } + finally + { + window.Close(); + } + } + + [AvaloniaFact] + public void OllamaModelsTabControl_LayoutAudit() + { + var vm = new MainViewModel(); + var control = new OllamaModelsTabControl { DataContext = vm.Ollama }; + var window = new Window { Content = control, Width = 1280, Height = 800 }; + try + { + window.Show(); + + var auditor = new LayoutAuditor(); + var report = auditor.Audit(control); + + _output.WriteLine($"OllamaModelsTabControl Audit - Health={report.HealthScore}/100, Violations={report.Violations.Count}"); + if (report.Violations.Count > 0) + { + _output.WriteLine(report.ToDetailedReport()); + } + + Assert.NotNull(report); + } + finally + { + window.Close(); + } + } + + [AvaloniaFact] + public void SettingsTabControl_LayoutAudit() + { + var vm = new MainViewModel(); + var control = new SettingsTabControl { DataContext = vm.Settings }; + var window = new Window { Content = control, Width = 1280, Height = 800 }; + try + { + window.Show(); + + var auditor = new LayoutAuditor(); + var report = auditor.Audit(control); + + _output.WriteLine($"SettingsTabControl Audit - Health={report.HealthScore}/100, Violations={report.Violations.Count}"); + if (report.Violations.Count > 0) + { + _output.WriteLine(report.ToDetailedReport()); + } + + Assert.NotNull(report); + } + finally + { + window.Close(); + } + } +} diff --git a/LocalLLMServerManager.Tests/LocalLLMServerManager.Tests.csproj b/LocalLLMServerManager.Tests/LocalLLMServerManager.Tests.csproj index d333b80..2003c47 100644 --- a/LocalLLMServerManager.Tests/LocalLLMServerManager.Tests.csproj +++ b/LocalLLMServerManager.Tests/LocalLLMServerManager.Tests.csproj @@ -32,6 +32,7 @@ + diff --git a/package.json b/package.json index 85f18a5..381d393 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,7 @@ "docs:build": "vitepress build docs", "docs:preview": "vitepress preview docs", "update": "pwsh -ExecutionPolicy Bypass -File ./scripts/fast_update.ps1", - "build:installer": "pwsh -ExecutionPolicy Bypass -File ./scripts/build_release.ps1", - "test:layout": "playwright test --config=playwright.config.ts" + "build:installer": "pwsh -ExecutionPolicy Bypass -File ./scripts/build_release.ps1" }, "devDependencies": { "@eslint/js": "^10.0.1", @@ -19,7 +18,6 @@ "@types/node": "^26.6.1", "eslint": "^10.10.0", "mermaid": "^12.0.0", - "playwright-layout-inspector": "github:spelech/playwright-layout-inspector#c042a04", "typescript": "^5.7.0", "typescript-eslint": "^8.70.0", "vitepress": "^1.6.3", diff --git a/playwright.config.ts b/playwright.config.ts deleted file mode 100644 index 28d7389..0000000 --- a/playwright.config.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { defineConfig, devices } from '@playwright/test'; - -export default defineConfig({ - testDir: './tests/layout-inspector', - fullyParallel: true, - forbidOnly: !!process.env.CI, - retries: process.env.CI ? 2 : 0, - workers: process.env.CI ? 1 : undefined, - reporter: 'list', - use: { - baseURL: process.env.TEST_BASE_URL || 'http://localhost:5000', - trace: 'on-first-retry', - }, - projects: [ - { - name: 'Desktop 1080p', - use: { - viewport: { width: 1920, height: 1080 }, - }, - }, - { - name: 'Desktop 1440p', - use: { - viewport: { width: 2560, height: 1440 }, - }, - }, - { - name: 'Tablet iPad', - use: { - ...devices['iPad Pro 11'], - }, - }, - { - name: 'Mobile Galaxy S25+', - use: { - viewport: { width: 412, height: 915 }, - deviceScaleFactor: 2.625, - isMobile: true, - hasTouch: true, - defaultBrowserType: 'chromium', - }, - }, - ], -}); diff --git a/tests/layout-inspector/declarations.d.ts b/tests/layout-inspector/declarations.d.ts deleted file mode 100644 index 51b6261..0000000 --- a/tests/layout-inspector/declarations.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -declare global { - namespace PlaywrightTest { - interface Matchers { - toHaveNoLayoutOverflow(): R; - toHaveMobileFit(): R; - toHaveTouchFriendlyTargets(options?: { minSize?: number }): R; - } - } -} - -export {}; diff --git a/tests/layout-inspector/layout-audit.spec.ts b/tests/layout-inspector/layout-audit.spec.ts deleted file mode 100644 index b4d92d9..0000000 --- a/tests/layout-inspector/layout-audit.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { test, expect } from '@playwright/test'; -import 'playwright-layout-inspector/matchers'; - -test.describe('L³M² Web Dashboard Layout & UX Audit', () => { - test.beforeEach(async ({ page }) => { - await page.goto('/'); - await page.waitForSelector('#out', { timeout: 15000 }); - }); - - test('assert zero horizontal viewport overflow and canvas bleeding', async ({ page }) => { - await expect(page).toHaveNoLayoutOverflow(); - }); - - test('assert viewport and mobile fit standards', async ({ page }) => { - await expect(page).toHaveMobileFit(); - }); - - test('assert interactive touch targets meet ergonomics standards', async ({ page }) => { - await expect(page).toHaveTouchFriendlyTargets({ minSize: 24 }); - }); -}); diff --git a/tests/layout-inspector/matchers.d.ts b/tests/layout-inspector/matchers.d.ts deleted file mode 100644 index aba4dbd..0000000 --- a/tests/layout-inspector/matchers.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import '@playwright/test'; - -declare global { - namespace PlaywrightTest { - interface Matchers { - toHaveNoLayoutOverflow(): Promise; - toHaveMobileFit(): Promise; - toHaveTouchFriendlyTargets(options?: { minSize?: number }): Promise; - } - } -} From a13e878c142b1e29cf7abdcf42e7c9ab19772461 Mon Sep 17 00:00:00 2001 From: Steven Pelech Date: Sun, 20 Sep 2026 22:51:36 -0500 Subject: [PATCH 4/7] fix(tooling): prune unused playwright test dependency and clean up audit test usings --- LocalLLMServerManager.Tests/AvaloniaLayoutAuditTests.cs | 3 --- package.json | 1 - 2 files changed, 4 deletions(-) diff --git a/LocalLLMServerManager.Tests/AvaloniaLayoutAuditTests.cs b/LocalLLMServerManager.Tests/AvaloniaLayoutAuditTests.cs index 04dfba5..4f82014 100644 --- a/LocalLLMServerManager.Tests/AvaloniaLayoutAuditTests.cs +++ b/LocalLLMServerManager.Tests/AvaloniaLayoutAuditTests.cs @@ -1,8 +1,5 @@ -using System; -using System.Linq; using Avalonia.Controls; using Avalonia.Headless.XUnit; -using Avalonia.LayoutInspector.Assertions; using Avalonia.LayoutInspector.Engine; using Avalonia.LayoutInspector.Models; using Avalonia.LayoutInspector.Responsive; diff --git a/package.json b/package.json index 381d393..84df328 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,6 @@ }, "devDependencies": { "@eslint/js": "^10.0.1", - "@playwright/test": "^1.63.0", "@types/node": "^26.6.1", "eslint": "^10.10.0", "mermaid": "^12.0.0", From 4f4811bed3dd12605e19a44f8b26a0c03018ec86 Mon Sep 17 00:00:00 2001 From: Steven Pelech Date: Sun, 20 Sep 2026 23:08:19 -0500 Subject: [PATCH 5/7] chore(release): bump version to v3.16.0 and update release assets --- App.axaml | 2 +- Endpoints/HealthEndpoints.cs | 2 +- .../LocalLLMServerManager.Shared.csproj | 6 +++--- .../AvaloniaHeadlessInteractionTests.cs | 2 +- LocalLLMServerManager.Tests/PlaywrightWasmE2ETests.cs | 2 +- LocalLLMServerManager.Tests/WasmAssetFreshnessTests.cs | 2 +- LocalLLMServerManager.Web/LocalLLMServerManager.Web.csproj | 6 +++--- LocalLLMServerManager.Web/main.js | 2 +- LocalLLMServerManager.csproj | 6 +++--- README.md | 3 ++- Views/MainWindow.axaml | 4 ++-- docs/USER_GUIDE.md | 2 +- scripts/build_release.ps1 | 4 ++-- scripts/installer.iss | 4 ++-- wwwroot/index.html | 2 +- wwwroot/main.js | 2 +- 16 files changed, 26 insertions(+), 25 deletions(-) diff --git a/App.axaml b/App.axaml index c7598e1..ae42946 100644 --- a/App.axaml +++ b/App.axaml @@ -16,7 +16,7 @@ - + diff --git a/Endpoints/HealthEndpoints.cs b/Endpoints/HealthEndpoints.cs index 8c17e4f..86cdf6e 100644 --- a/Endpoints/HealthEndpoints.cs +++ b/Endpoints/HealthEndpoints.cs @@ -29,7 +29,7 @@ public static void MapHealthEndpoints(this WebApplication app) StableDiffusion = forgeHealthy ? "Online" : "Offline", ComfyUI = comfyHealthy ? "Online" : "Offline", PreferredImageEngine = settings.PreferredImageEngine, - Version = "3.15.1", + Version = "3.16.0", }); }); } diff --git a/LocalLLMServerManager.Shared/LocalLLMServerManager.Shared.csproj b/LocalLLMServerManager.Shared/LocalLLMServerManager.Shared.csproj index 9b4f059..266de56 100644 --- a/LocalLLMServerManager.Shared/LocalLLMServerManager.Shared.csproj +++ b/LocalLLMServerManager.Shared/LocalLLMServerManager.Shared.csproj @@ -4,9 +4,9 @@ net10.0 enable enable - 3.15.1 - 3.15.1.0 - 3.15.1.0 + 3.16.0 + 3.16.0.0 + 3.16.0.0 true diff --git a/LocalLLMServerManager.Tests/AvaloniaHeadlessInteractionTests.cs b/LocalLLMServerManager.Tests/AvaloniaHeadlessInteractionTests.cs index a85c8e4..62f1ba2 100644 --- a/LocalLLMServerManager.Tests/AvaloniaHeadlessInteractionTests.cs +++ b/LocalLLMServerManager.Tests/AvaloniaHeadlessInteractionTests.cs @@ -37,7 +37,7 @@ public void MainView_RendersVisualTree_AndBindsVersionCorrectly() var versionTextBlock = textBlocks.FirstOrDefault(t => t.Text != null && t.Text.Contains("LocalLLMServerManager v")); Assert.NotNull(versionTextBlock); - Assert.Contains("v3.15.1", versionTextBlock.Text); + Assert.Contains("v3.16.0", versionTextBlock.Text); window.Close(); } diff --git a/LocalLLMServerManager.Tests/PlaywrightWasmE2ETests.cs b/LocalLLMServerManager.Tests/PlaywrightWasmE2ETests.cs index 0a369b2..ffe3f88 100644 --- a/LocalLLMServerManager.Tests/PlaywrightWasmE2ETests.cs +++ b/LocalLLMServerManager.Tests/PlaywrightWasmE2ETests.cs @@ -75,7 +75,7 @@ public async Task WebDashboard_BootsCleanlyWithoutConsoleOr404Errors() Assert.True(consoleErrors.IsEmpty, $"Errors:\n{string.Join("\n", consoleErrors)}\nOut HTML:\n{outHtml}"); Assert.NotNull(outputContainer); Assert.True(canvas != null, $"Canvas element not found in DOM! Container HTML: {outHtml}"); - Assert.Equal("3.15.1", loadedVersion); + Assert.Equal("3.16.0", loadedVersion); // Exercise interactive browser pointer & keyboard events var boundingBox = await canvas.BoundingBoxAsync(); diff --git a/LocalLLMServerManager.Tests/WasmAssetFreshnessTests.cs b/LocalLLMServerManager.Tests/WasmAssetFreshnessTests.cs index 351fca5..30bbe52 100644 --- a/LocalLLMServerManager.Tests/WasmAssetFreshnessTests.cs +++ b/LocalLLMServerManager.Tests/WasmAssetFreshnessTests.cs @@ -52,7 +52,7 @@ public void MainJs_VersionStringMatchesCurrentVersion() var mainJsPath = Path.Combine(root, "wwwroot", "main.js"); var webMainJsPath = Path.Combine(root, "LocalLLMServerManager.Web", "main.js"); - var expectedVersion = typeof(MainViewModel).Assembly.GetName().Version?.ToString(3) ?? "3.15.1"; + var expectedVersion = typeof(MainViewModel).Assembly.GetName().Version?.ToString(3) ?? "3.16.0"; foreach (var path in new[] { mainJsPath, webMainJsPath }) { diff --git a/LocalLLMServerManager.Web/LocalLLMServerManager.Web.csproj b/LocalLLMServerManager.Web/LocalLLMServerManager.Web.csproj index de72f20..ab7f692 100644 --- a/LocalLLMServerManager.Web/LocalLLMServerManager.Web.csproj +++ b/LocalLLMServerManager.Web/LocalLLMServerManager.Web.csproj @@ -4,9 +4,9 @@ net10.0 enable enable - 3.15.1 - 3.15.1.0 - 3.15.1.0 + 3.16.0 + 3.16.0.0 + 3.16.0.0 main.js Exe true diff --git a/LocalLLMServerManager.Web/main.js b/LocalLLMServerManager.Web/main.js index 127a3f5..961772d 100644 --- a/LocalLLMServerManager.Web/main.js +++ b/LocalLLMServerManager.Web/main.js @@ -5,7 +5,7 @@ if (!is_browser) { throw new Error(`Expected to be running in a browser`); } -const APP_VERSION = "3.15.1"; +const APP_VERSION = "3.16.0"; globalThis.getOrigin = function () { return window.location.origin; diff --git a/LocalLLMServerManager.csproj b/LocalLLMServerManager.csproj index 43a6391..6fccce6 100644 --- a/LocalLLMServerManager.csproj +++ b/LocalLLMServerManager.csproj @@ -11,9 +11,9 @@ MINOR — new user-facing features (bump per feature PR) PATCH — bug fixes, dependency updates, doc-only changes --> - 3.15.1 - 3.15.1.0 - 3.15.1.0 + 3.16.0 + 3.16.0.0 + 3.16.0.0 Assets\app-icon.ico true diff --git a/README.md b/README.md index 0e972d5..f3e44a6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Local LLM Server Manager -> **v3.15.1** — The unified orchestrator for local AI. Manage Large Language Models (**Ollama**), Image Generation (**Stable Diffusion Forge & ComfyUI**), **3D Mesh Generation**, **Video Generation**, and **Audio & Speech Synthesis (Kokoro TTS)** from a single desktop dashboard, background daemon, and Model Context Protocol (MCP) server. +> **v3.16.0** — The unified orchestrator for local AI. Manage Large Language Models (**Ollama**), Image Generation (**Stable Diffusion Forge & ComfyUI**), **3D Mesh Generation**, **Video Generation**, and **Audio & Speech Synthesis (Kokoro TTS)** from a single desktop dashboard, background daemon, and Model Context Protocol (MCP) server. > > Designed with the **`L³M²`** Matte Carbon design system, real-time GPU VRAM telemetry, automated memory management, and magnetic multi-window support on Windows and Linux. @@ -350,6 +350,7 @@ We use **MAJOR.MINOR.PATCH** (SemVer): | `3.11.0` | Dynamic WebAssembly browser origin resolution via JSImport, centralized `HttpHelper` with `BaseAddress` validation, thread-safe model collection synchronization, dynamic engine health status indicators, headless UI interaction test suite, and enhanced browser E2E test harness | | `3.15.0` | Magnetic Companion Windows (`WindowSnapManager`) with lockstep dragging, proximity snap, and multi-monitor detach; in-app AI Assist with LiteLLM capability discovery badges and multimodal screenshot analysis; Real Engine Test Flight verification runner for Text, Image, Video, and Audio backends; auto-detected LAN IP endpoints with LAN MCP URLs; optimized SettingsService async caching and hardware JSON lookup performance | | `3.15.1` | Consolidated dependency updates across NuGet, npm, and GitHub Actions; configured Dependabot grouped updates to prevent PR clutter; updated Microsoft.NET.Test.Sdk (18.10.1), Microsoft.Playwright (1.62.0), ESLint 10, TypeScript-ESLint 8.70, and actions/checkout@v7; synchronized WASM UI distribution | +| `3.16.0` | Replaced deprecated WASM Playwright layout inspector with native Avalonia.LayoutInspector test suite; integrated automated headless layout auditing for MainWindow and UI tabs across Desktop, Tablet, and Mobile viewports; pruned legacy Playwright test dependencies | --- diff --git a/Views/MainWindow.axaml b/Views/MainWindow.axaml index f788d03..9cd64a6 100644 --- a/Views/MainWindow.axaml +++ b/Views/MainWindow.axaml @@ -8,7 +8,7 @@ x:Class="LocalLLMServerManager.Views.MainWindow" x:DataType="vm:MainViewModel" Icon="avares://LocalLLMServerManager/Assets/app-icon.ico" - Title="Local LLM Server Manager v3.15.1" + Title="Local LLM Server Manager v3.16.0" Width="1280" Height="840" MinWidth="1024" MinHeight="700" WindowStartupLocation="CenterScreen" @@ -55,7 +55,7 @@ - + diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index c124fa5..8a9e581 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -1,6 +1,6 @@ # Local LLM Server Manager — User Guide Hub -Welcome to the **Local LLM Server Manager (v3.15.1)** User Guide. This document provides a complete guide to operating the dashboard, configuring local AI engines, and using generative studio tools. +Welcome to the **Local LLM Server Manager (v3.16.0)** User Guide. This document provides a complete guide to operating the dashboard, configuring local AI engines, and using generative studio tools. --- diff --git a/scripts/build_release.ps1 b/scripts/build_release.ps1 index 0cee82e..0d09695 100644 --- a/scripts/build_release.ps1 +++ b/scripts/build_release.ps1 @@ -1,8 +1,8 @@ -# LocalLLMServerManager v3.15.1 — Release Build & Package Script +# LocalLLMServerManager v3.16.0 — Release Build & Package Script # Builds Win-x64 Desktop exe, Linux-x64 SingleFile daemon, and Browser-WASM distribution. param( - [string]$Version = "3.15.1" + [string]$Version = "3.16.0" ) $ErrorActionPreference = "Stop" diff --git a/scripts/installer.iss b/scripts/installer.iss index c3af2cd..337341e 100644 --- a/scripts/installer.iss +++ b/scripts/installer.iss @@ -1,7 +1,7 @@ -; Script generated for Inno Setup - LocalLLMServerManager v3.15.1 +; Script generated for Inno Setup - LocalLLMServerManager v3.16.0 ; Packaging Self-Contained Win-x64 Release Executable & Static Wasm UI #define MyAppName "Local LLM Server Manager" -#define MyAppVersion "3.15.1" +#define MyAppVersion "3.16.0" #define MyAppPublisher "LocalLLMServerManager Team" #define MyAppURL "https://github.com/spelech/LocalLLMServerManager" #define MyAppExeName "LocalLLMServerManager.exe" diff --git a/wwwroot/index.html b/wwwroot/index.html index 1c547f2..0083a9b 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -44,7 +44,7 @@
- + diff --git a/wwwroot/main.js b/wwwroot/main.js index 127a3f5..961772d 100644 --- a/wwwroot/main.js +++ b/wwwroot/main.js @@ -5,7 +5,7 @@ if (!is_browser) { throw new Error(`Expected to be running in a browser`); } -const APP_VERSION = "3.15.1"; +const APP_VERSION = "3.16.0"; globalThis.getOrigin = function () { return window.location.origin; From 00754841e43fc2629e206f586f15951694c3c5af Mon Sep 17 00:00:00 2001 From: Steven Pelech Date: Sun, 20 Sep 2026 23:13:05 -0500 Subject: [PATCH 6/7] ci: checkout Avalonia.LayoutInspector repository for workflow builds --- .github/workflows/ci.yml | 3 +++ .github/workflows/release.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1720fa6..8151715 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,9 @@ jobs: - name: Checkout Code uses: actions/checkout@v7 + - name: Checkout Avalonia.LayoutInspector + run: git clone https://github.com/spelech/Avalonia.LayoutInspector.git ../Avalonia.LayoutInspector + - name: Setup .NET SDK 10.0 uses: actions/setup-dotnet@v6 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6a17fdf..0724b60 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,6 +17,9 @@ jobs: - name: Checkout Code uses: actions/checkout@v7 + - name: Checkout Avalonia.LayoutInspector + run: git clone https://github.com/spelech/Avalonia.LayoutInspector.git ../Avalonia.LayoutInspector + - name: Setup .NET 10 SDK uses: actions/setup-dotnet@v6 with: From 8ee290b511d3238f458b3d816322bcc1eee1f6a1 Mon Sep 17 00:00:00 2001 From: Steven Pelech Date: Sun, 20 Sep 2026 23:32:58 -0500 Subject: [PATCH 7/7] feat(ui): implement in-app slide-out drawers, stacked chat composer, overflow menu, and layout polish --- .../ViewModels/AiAssistantViewModel.cs | 12 ++- .../ViewModels/DocumentationViewModel.cs | 5 +- .../ViewModels/MainViewModel.cs | 26 ++++++ .../Controls/AiAssistantTabControl.axaml | 83 +++++++++++-------- .../Controls/OllamaModelsTabControl.axaml | 4 +- .../Views/Controls/SettingsTabControl.axaml | 4 +- .../Views/MainView.axaml | 59 +++++++++++-- .../Views/MainView.axaml.cs | 9 ++ .../AvaloniaHeadlessInteractionTests.cs | 15 +++- .../AvaloniaLayoutAuditTests.cs | 64 +++++++++++--- .../MainViewModelTests.cs | 59 +++++++++++++ Views/MainWindow.axaml | 2 +- 12 files changed, 280 insertions(+), 62 deletions(-) diff --git a/LocalLLMServerManager.Shared/ViewModels/AiAssistantViewModel.cs b/LocalLLMServerManager.Shared/ViewModels/AiAssistantViewModel.cs index 0998b74..a96bccf 100644 --- a/LocalLLMServerManager.Shared/ViewModels/AiAssistantViewModel.cs +++ b/LocalLLMServerManager.Shared/ViewModels/AiAssistantViewModel.cs @@ -38,10 +38,20 @@ public partial class AiAssistantViewModel : ObservableObject public Action? OnPopOutNativeWindowRequested { get; set; } + [ObservableProperty] private bool _isDrawerOpen = false; + [RelayCommand] public void RequestPopOut() { - OnPopOutNativeWindowRequested?.Invoke(); + if (OnPopOutNativeWindowRequested != null) + { + IsDrawerOpen = false; + OnPopOutNativeWindowRequested.Invoke(); + } + else + { + IsDrawerOpen = !IsDrawerOpen; + } } [RelayCommand] diff --git a/LocalLLMServerManager.Shared/ViewModels/DocumentationViewModel.cs b/LocalLLMServerManager.Shared/ViewModels/DocumentationViewModel.cs index a5f15ac..7173c35 100644 --- a/LocalLLMServerManager.Shared/ViewModels/DocumentationViewModel.cs +++ b/LocalLLMServerManager.Shared/ViewModels/DocumentationViewModel.cs @@ -57,16 +57,19 @@ public partial class DocumentationViewModel : ObservableObject public Action? OnNavigateToTabRequested { get; set; } public Action? OnPopOutNativeWindowRequested { get; set; } + [ObservableProperty] private bool _isDrawerOpen = false; + [RelayCommand] public void PopOutNativeWindow() { if (OnPopOutNativeWindowRequested != null) { + IsDrawerOpen = false; OnPopOutNativeWindowRequested.Invoke(); } else { - ToggleFloatingOverlay(); + IsDrawerOpen = !IsDrawerOpen; } } diff --git a/LocalLLMServerManager.Shared/ViewModels/MainViewModel.cs b/LocalLLMServerManager.Shared/ViewModels/MainViewModel.cs index ad41ace..81497d9 100644 --- a/LocalLLMServerManager.Shared/ViewModels/MainViewModel.cs +++ b/LocalLLMServerManager.Shared/ViewModels/MainViewModel.cs @@ -98,6 +98,32 @@ public HttpClient Http [ObservableProperty] private int _selectedModelsTabIndex = 0; + [ObservableProperty] private bool _isAnyDrawerOpen = false; + + [RelayCommand] + public void ToggleDocumentationDrawer() + { + Assistant.IsDrawerOpen = false; + Documentation.IsDrawerOpen = !Documentation.IsDrawerOpen; + IsAnyDrawerOpen = Documentation.IsDrawerOpen; + } + + [RelayCommand] + public void ToggleAiAssistDrawer() + { + Documentation.IsDrawerOpen = false; + Assistant.IsDrawerOpen = !Assistant.IsDrawerOpen; + IsAnyDrawerOpen = Assistant.IsDrawerOpen; + } + + [RelayCommand] + public void CloseDrawers() + { + Documentation.IsDrawerOpen = false; + Assistant.IsDrawerOpen = false; + IsAnyDrawerOpen = false; + } + [ObservableProperty] private string _appVersionText = $"LocalLLMServerManager v{typeof(MainViewModel).Assembly.GetName().Version?.ToString(3) ?? "3.15.1"} — Unified WASM & Desktop UI"; diff --git a/LocalLLMServerManager.Shared/Views/Controls/AiAssistantTabControl.axaml b/LocalLLMServerManager.Shared/Views/Controls/AiAssistantTabControl.axaml index d1386fb..8392b59 100644 --- a/LocalLLMServerManager.Shared/Views/Controls/AiAssistantTabControl.axaml +++ b/LocalLLMServerManager.Shared/Views/Controls/AiAssistantTabControl.axaml @@ -43,9 +43,15 @@ @@ -268,37 +274,33 @@ - - - @@ -98,6 +98,53 @@ + + + + + + + + + + + + + +