# AvParser Каркас desktop-приложения на **Avalonia 12** с ReactiveUI-MVVM, адаптивным layout поверх Semi.Avalonia, единым DI-контейнером и тремя уровнями тестов. Доменная часть пока намеренно абстрактная: ядро — это pluggable-контракт `IParser` и два демо-парсера, чтобы каркас был запускаемым и проверяемым end-to-end до появления настоящей логики. --- ## Быстрый старт Нужен .NET SDK **10.0.100** (закреплён в `global.json`). ```bash dotnet restore AvParser.slnx ``` ```bash dotnet build AvParser.slnx -c Release ``` ```bash dotnet test AvParser.slnx -c Release ``` ```bash dotnet run --project src/AvParser.Desktop ``` Форматирование (csharpier — единственный владелец форматирования, включая `.axaml` и `.csproj`): ```bash dotnet tool restore && dotnet csharpier check . ``` --- ## Структура ``` src/ AvParser.Core домен: IParser, IParserCatalog, модели, демо-парсеры ноль зависимостей кроме DI.Abstractions — ни Avalonia, ни IO AvParser.Infrastructure AppPaths, JSON-настройки с debounce, Serilog AvParser.UI Avalonia class library: App-независимые View, ViewModel, ResponsiveLayout, дизайн-токены, навигация AvParser.Desktop WinExe-хост: Program.cs, App.axaml, composition root tests/ AvParser.Core.Tests парсеры, реестр, отмена, прогресс AvParser.UI.Tests ViewModel'и без Avalonia AvParser.UI.HeadlessTests реальное дерево контролов через [AvaloniaFact] ``` Ссылки идут строго в одну сторону: `Core ← Infrastructure ← UI ← Desktop`. `UI` — библиотека, а не exe, именно чтобы headless-тесты собирали настоящие View, не подтягивая `Program.cs`, Serilog и контейнер. --- ## Адаптивный layout В Avalonia нет `AdaptiveTrigger`, `VisualStateManager` и media-queries. Есть три примитива: наблюдаемый `Visual.Bounds`, псевдоклассы и `SplitView`. `ResponsiveLayout` связывает первое со вторым — получается CSS-подобная реакция на ширину. | Брейкпоинт | Ширина окна | Навигация | |---|---|---| | Compact | < 720 px | выезжающий drawer поверх контента | | Medium | 720 – 1100 px | рельс из одних иконок (56 px) | | Expanded | ≥ 1100 px | полный сайдбар с подписями (248 px) | Переключение с гистерезисом в 24 px: без неё перетаскивание края окна заставляет layout мигать между двумя состояниями на каждом пикселе дрожания. Разделение обязанностей, которое важно не сломать: - `SplitView.DisplayMode` и `IsPaneOpen` **биндятся во ViewModel**. Style-сеттер навсегда проигрывает локальному значению, поэтому первый же клик по гамбургеру заморозил бы любой стиль, который тоже пишет в эти свойства. - Всё чисто визуальное — ширины панели, видимость подписей, паддинги — живёт в `Styles/Shell.axaml`. Селекторы там написаны как `:is(UserControl).shell`, а не `UserControl.shell`: селектор типа в Avalonia матчит **точный** тип, а `ShellView` наследуется от `ReactiveUserControl` — обычная форма молча не сматчилась бы ни с чем. На это есть тест (`ShellViewTests.The_shell_stylesheet_is_actually_applied`). --- ## Дизайн-токены Все цвета, отступы, радиусы и типографика — в `Styles/Tokens.axaml`, с отдельными словарями для Light и Dark. В остальном XAML нет ни одного литерального цвета и ни одного «магического» отступа, так что перекрасить тему или уплотнить интерфейс — это правка одного файла. Semi.Avalonia даёт темы контролов; токены — это семантический слой приложения поверх них. Кнопки `.primary` / `.destructive` описаны своими стилями, а не классами Semi, чтобы акцентный цвет не разъезжался между двумя палитрами. --- ## Стек | Пакет | Версия | Заметка | |---|---|---| | Avalonia | 12.1.1 | compiled bindings по умолчанию → `x:DataType` обязателен | | ReactiveUI.Avalonia | 12.1.1 | `Avalonia.ReactiveUI` — deprecated, это его преемник | | ReactiveUI | 24.1.0 | дистрибутив Primitives: `RxVoid` вместо `Unit`, `ISequencer` вместо `IScheduler` | | Semi.Avalonia | 12.1.0.1 | темы контролов | | xUnit | v3 (3.2.2) | `Avalonia.Headless.XUnit` 12.x требует именно v3 | --- ## Что проверить руками 1. Потянуть окно по ширине — сайдбар проходит путь `полный → только иконки → выезжающий drawer`, без мигания на границах. 2. Переключить тему кнопкой в заголовке и в Settings; перезапустить — выбор сохранился. 3. На странице Parse нажать **50k rows**, затем **Parse** — виден прогресс; **Cancel** останавливает на середине и пишет, сколько успело разобраться. 4. `F12` в Debug-сборке открывает Avalonia DevTools — там видно, как переключаются `:compact` / `:medium` / `:expanded`. Настройки и логи лежат в `%APPDATA%/AvParser` (Windows) или `~/.config/AvParser` (Linux/macOS).