Files
av-parser/CLAUDE.md
T
Leonid PershinandClaude Opus 5 aeafe0af36 Add build and run scripts; drop the broken Avalonia.Diagnostics reference
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>
2026-08-13 16:32:16 +03:00

130 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
Конвенции этого репозитория и грабли, на которые здесь уже наступили. Читать до правок.
## Команды
Полный локальный гейт (формат → сборка → тесты) — то, что нужно прогнать перед коммитом:
```bash
./build.ps1
```
Отдельные шаги, если нужен только один из них:
```bash
dotnet build AvParser.slnx -c Release
```
```bash
dotnet test AvParser.slnx -c Release
```
```bash
dotnet csharpier check .
```
```bash
./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.** Логика туда не переезжает.
## Добавить парсер
1. Реализовать `ITextParser` в `Core/Parsing/`.
2. Одна строка в `CoreServiceCollectionExtensions.AddAvParserCore()`.
Всё. `IParserCatalog`, страница Parse и выпадающий список подхватят его сами.
## Добавить страницу
1. Наследник `PageViewModel` в `UI/ViewModels/XxxViewModel.cs` (`Title`, `IconKey`).
2. `UI/Views/XxxView.axaml` — имя обязано соответствовать конвенции `ViewLocator`:
`...ViewModels.XxxViewModel``...Views.XxxView`.
3. Регистрация в `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.XUnit` 12.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`.