A wrapper around `dotnet build` earns nothing, so build.ps1/build.sh are the full local gate instead — tool restore, package restore, format check, build, test — in the order that fails cheapest first, with a non-zero exit on failure. run.ps1/run.sh default to Debug and pass extra arguments through to the app. Both flavours ship because the Desktop head targets Windows, Linux and macOS. Writing them immediately paid for itself: the very first run failed restore on Avalonia.Diagnostics 12.1.1, which does not exist — the package stops at 11.3.x because Avalonia 12 moved the inspector into a separate tool with its own installation. The reference had survived because it sat behind Condition="'$(Configuration)' == 'Debug'", and `dotnet restore` evaluates with the default configuration while every build so far had passed -c Release. So `dotnet build -c Release` worked and a bare `dotnet restore` did not. Reference removed rather than replaced: AvaloniaUI.DiagnosticsSupport pulls in a separately installed tool, which is not a dependency to add to a skeleton without asking. README and CLAUDE.md no longer promise F12, and both traps are written down where the next person will hit them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
142 lines
7.3 KiB
Markdown
142 lines
7.3 KiB
Markdown
# AvParser
|
||
|
||
Каркас desktop-приложения на **Avalonia 12** с ReactiveUI-MVVM, адаптивным layout поверх
|
||
Semi.Avalonia, единым DI-контейнером и тремя уровнями тестов.
|
||
|
||
Доменная часть пока намеренно абстрактная: ядро — это pluggable-контракт
|
||
`IParser<TInput, TOutput>` и два демо-парсера, чтобы каркас был запускаемым и проверяемым
|
||
end-to-end до появления настоящей логики.
|
||
|
||
---
|
||
|
||
## Быстрый старт
|
||
|
||
Нужен .NET SDK **10.0.100** (закреплён в `global.json`).
|
||
|
||
Полный локальный гейт — восстановление, проверка форматирования, сборка, тесты:
|
||
|
||
```bash
|
||
./build.ps1
|
||
```
|
||
|
||
Запуск приложения (по умолчанию Debug):
|
||
|
||
```bash
|
||
./run.ps1
|
||
```
|
||
|
||
На Linux и macOS — `./build.sh` и `./run.sh`, аргументы те же.
|
||
|
||
Полезные флаги:
|
||
|
||
```bash
|
||
./build.ps1 -Fix -Configuration Debug
|
||
```
|
||
|
||
`-Fix` переформатирует код вместо того, чтобы падать на непрошедшей проверке; `-SkipTests`
|
||
собирает без прогона тестов. То же в bash: `--fix`, `-c Debug`, `--skip-tests`.
|
||
|
||
Если нужны отдельные шаги, скрипты ничего не прячут:
|
||
|
||
```bash
|
||
dotnet build AvParser.slnx -c Release
|
||
```
|
||
```bash
|
||
dotnet test AvParser.slnx -c Release
|
||
```
|
||
```bash
|
||
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<T>` — обычная
|
||
форма молча не сматчилась бы ни с чем. На это есть тест
|
||
(`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**
|
||
останавливает на середине и пишет, сколько успело разобраться.
|
||
В Avalonia 12 инспектора «из коробки» больше нет: `Avalonia.Diagnostics` остановился на 11.3.x,
|
||
а DevTools вынесли в отдельный инструмент со своей установкой
|
||
(`AvaloniaUI.DiagnosticsSupport` + `.WithDeveloperTools()`). Поэтому `F12` здесь ничего не
|
||
открывает — зависимость намеренно не добавлена.
|
||
|
||
Настройки и логи лежат в `%APPDATA%/AvParser` (Windows) или `~/.config/AvParser` (Linux/macOS).
|