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>
8.2 KiB
CLAUDE.md
Конвенции этого репозитория и грабли, на которые здесь уже наступили. Читать до правок.
Команды
Полный локальный гейт (формат → сборка → тесты) — то, что нужно прогнать перед коммитом:
./build.ps1
Отдельные шаги, если нужен только один из них:
dotnet build AvParser.slnx -c Release
dotnet test AvParser.slnx -c Release
dotnet csharpier check .
./run.ps1
На Linux/macOS — ./build.sh и ./run.sh с теми же шагами.
Слои
Core ← Infrastructure ← UI ← Desktop, строго в одну сторону.
AvParser.Coreне ссылается на Avalonia. Это единственное ограничение, которое здесь по-настоящему несущее: домен должен запускаться из CLI, worker-сервиса или бенчмарка. Как только Avalonia станет доступна из домена, кто-нибудь потянется кDispatcher.UIThreadилиIStorageProviderвнутри парсера.AvParser.UI— библиотека, а не exe. Headless-тесты собирают настоящие View, не подтягиваяProgram.cs, Serilog и контейнер.AvParser.Desktop— тонкий composition root. Логика туда не переезжает.
Добавить парсер
- Реализовать
ITextParserвCore/Parsing/. - Одна строка в
CoreServiceCollectionExtensions.AddAvParserCore().
Всё. IParserCatalog, страница Parse и выпадающий список подхватят его сами.
Добавить страницу
- Наследник
PageViewModelвUI/ViewModels/XxxViewModel.cs(Title,IconKey). UI/Views/XxxView.axaml— имя обязано соответствовать конвенцииViewLocator:...ViewModels.XxxViewModel→...Views.XxxView.- Регистрация в
AddAvParserUI(): конкретным типом и какPageViewModel— порядок этих регистраций и есть порядок пунктов в рельсе навигации.
ReactiveUI 24 (дистрибутив Primitives)
Это не классический ReactiveUI. System.Reactive не используется:
| Классика | Здесь |
|---|---|
Unit |
RxVoid |
IScheduler |
ISequencer (ReactiveUI.Primitives.Concurrency) |
Subject<T> / BehaviorSubject<T> |
Signal<T> / BehaviorSignal<T> |
RxApp.MainThreadScheduler |
RxSchedulers.MainThreadScheduler |
TestScheduler |
VirtualClock, ImmediateSequencer.Instance |
Привычные имена операторов (Select, Where, Throttle, DistinctUntilChanged,
CombineLatest) работают — Primitives отдаёт оба набора. using ReactiveUI.Primitives;
нужен ради Subscribe(Action<T>).
ReactiveUI 24 не инициализируется сама. Первый WhenAnyValue бросит
InvalidOperationException, пока не отработал builder. В приложении это делает
AppBuilder.UseReactiveUI(...); в проекте VM-тестов — module initializer
ReactiveUiBootstrap. Новый тестовый проект без Avalonia обязан сделать то же самое.
Конвенции ViewModel
- Каждая VM принимает
ISequencer? mainThread = nullи использует его вoutputScheduler:иToProperty(..., scheduler). Именно это делает тесты синхронными: они передаютImmediateSequencer.Instance. Без этого пришлось бы гонять диспетчер. - У VM с необязательным
ISequencerрегистрация в DI — явная фабрика, а не по типу: иначе выбор конструктора контейнером зависит от порядка регистраций. [Reactive]изReactiveUI.SourceGeneratorsна partial-свойствах; класс —partial.
Грабли, уже оплаченные
- Селектор типа в Avalonia матчит точный тип.
UserControl.shellне матчитShellView(наследникReactiveUserControl<T>) и молча не делает ничего. Использовать:is(UserControl).shell. Голый.shellтоже матчит, но тогда XAML-компилятор не может вывести тип дляSetterи падает с AVLN2200. - Style-сеттер навсегда проигрывает локальному значению. Не стилизовать
IsPaneOpenиDisplayMode— они биндятся во ViewModel. IPseudoClasses.Setтребует ведущего:.InvariantGlobalizationобязан бытьfalse: Semi.Avalonia строитCultureInfoв статическом конструкторе и падает целиком.- Превьюер рефлексирует безпараметровый статический
BuildAvaloniaApp(). Необязательный параметр ломает его вызов, вторая перегрузка —AmbiguousMatchException. - Compiled bindings включены по умолчанию (Avalonia 12):
x:DataTypeнужен на каждомUserControlи каждомDataTemplate. Avalonia.Headless.XUnit12.x — это xUnit v3, а не v2. Тестовые проекты —Exe.- Headless: ручной
Measure/Arrangeвнутри окна бесполезен — следующий проход layout окна вернёт свой размер. Задавать ширину самомуWindow. Но и без окна нельзя: у открепленного контрола не строится визуальное дерево. - csharpier — единственный владелец форматирования (включая
.axamlи.csproj).IDE0055понижен до suggestion: два форматтера сTreatWarningsAsErrorsдерутся насмерть. Condition="'$(Configuration)' == 'Debug'"наPackageReference— ловушка.dotnet restoreвычисляется с конфигурацией по умолчанию, поэтому пакет обязан резолвиться, даже если эту конфигурацию никто не собирает. Так тут проехал мёртвыйAvalonia.Diagnostics(его нет под Avalonia 12):dotnet build -c Releaseработал, а голыйdotnet restoreпадал.- Инспектора в Avalonia 12 нет из коробки.
Avalonia.Diagnosticsзакончился на 11.3.x; DevTools живут отдельно (AvaloniaUI.DiagnosticsSupport+.WithDeveloperTools()), со своей установкой. Зависимость намеренно не добавлена.
Качество
- Центральные версии пакетов —
Directory.Packages.props. НикакихVersion=в csproj (иначе NU1008 на restore). TreatWarningsAsErrorsвключён; NuGet-advisory (NU19xx) выведены из ошибок, чтобы свежая CVE не роняла сборку кода, который никто не трогал.- Тестовые послабления анализаторов — в
tests/Directory.Build.props, не в самих тестах.
Что осталось абстрактным
Домен — заглушка. IParser<TInput, TOutput> + DelimitedTextParser + KeyValueTextParser
существуют, чтобы каркас проверялся end-to-end. Когда появится настоящая доменная логика,
демо-парсеры удаляются вместе с их тестами и SampleFor/LargeSampleFor в ParseViewModel.