ReactiveUI (Agent Skills)

GitHub

Agent skills, system prompts, and AI developer rules for reactiveui/ReactiveUI

0 stars Code 2 Rule Files Full Docs MCP View JSON API

CLAUDE.md

# CLAUDE.md

This file is the single source of truth for AI/agent assistance in this repository (Claude Code, GitHub Copilot, and other coding agents). It consolidates build/test commands, architecture context, coding standards, and AOT guidance.

If there is any conflict between other agent instruction files and this file, follow **CLAUDE.md**.

---

## Repository Orientation

- **Repository root**
- **Primary working directory for build/test:** `./src`
- **Main solution:** `src/reactiveui.slnx`
- **Benchmarks solution:** `Benchmarks/ReactiveUI.Benchmarks.sln`
- **Integration tests:** `integrationtests/` (platform-specific solutions; not required for most tasks)

### Full Clone Required

**CRITICAL:** Use a full, recursive clone. Shallow clones can fail because build/versioning relies on git history. If a clone has already been done you must use the unshallow commit command in git.

```bash
git clone --recursive https://github.com/reactiveui/reactiveui.git
````

---

## Solution Format: SLNX

This repository uses **SLNX** (XML-based solution format) instead of legacy `.sln`.

* Introduced in Visual Studio 2022 17.10+
* Rider 2024.1+ support
* Works with `dotnet build/test` the same way `.sln` does
* Main file: `src/reactiveui.slnx`

---

## Build Environment Requirements

### Required SDKs

* .NET **8.0**, **9.0**, **10.0** SDKs (all required)

### Workload Restore (Required)

**CRITICAL:** Platform workloads must be restored or the build will fail. Run from the `./src` directory.

```powershell
dotnet --info

cd src
dotnet workload restore
cd ..
```

### Restore & Build

**CRITICAL:** Run build/test commands from `./src` unless the command explicitly uses `src/`-prefixed paths.

```powershell
cd src

dotnet restore reactiveui.slnx

dotnet build reactiveui.slnx -c Release
dotnet build reactiveui.slnx -c Release -warnaserror

dotnet clean reactiveui.slnx
```

### Windows Requirements

Building the full solution requires **Windows** due to Windows-only target frameworks (WPF, WinUI, .NET Framework). Non-Windows builds may fail; this is expected. In non-Windows environments, focus on documentation, targeted library changes, or analysis that does not require full compilation.

---

## Testing: Microsoft Testing Platform (MTP) + TUnit

This repo uses **Microsoft Testing Platform (MTP)** with **TUnit**. This differs from VSTest.

* MTP is configured via `global.json`
* Additional test settings in `testconfig.json`
* Test projects enable `TestingPlatformDotnetTestSupport` in `Directory.Build.props`

**Key rule:** TUnit/MTP arguments go **after** `--`.

### Testing Best Practices

* **Do NOT use `--no-build`**. Always build before testing to avoid stale binaries.
* To see test output, use `--output Detailed` **before** `--`.
* Repository configuration runs tests **non-parallel** (`"parallel": false` in `testconfig.json`) to avoid interference.

### Test Commands (run from `./src`)

```powershell
cd src

# Run all tests
dotnet test --solution reactiveui.slnx -c Release

# Run tests for a specific project
dotnet test --project tests/ReactiveUI.Tests/ReactiveUI.Tests.csproj

# Run with code coverage (Microsoft Code Coverage)
dotnet test --solution reactiveui.slnx --coverage --coverage-output-format cobertura

# Detailed output (place BEFORE --)
dotnet test --solution reactiveui.slnx -- --output Detailed
dotnet test --solution reactiveui.slnx --coverage --coverage-output-format cobertura -- --report-trx --output Detailed

# List tests
dotnet test --project tests/ReactiveUI.Tests/ReactiveUI.Tests.csproj -- --list-tests

# Fail fast
dotnet test --solution reactiveui.slnx -- --fail-fast

# Limit parallelism if needed (even though repo defaults non-parallel)
dotnet test --solution reactiveui.slnx -- --maximum-parallel-tests 4
```

### TUnit `--treenode-filter` Syntax

Pattern: `/{AssemblyName}/{Namespace}/{ClassName}/{TestMethodName}`

Examples:

```powershell
# Single test
dotnet test --project tests/ReactiveUI.Tests/ReactiveUI.Tests.csproj -- --treenode-filter "/*/*/*/MyTestMethod"

# All tests in class
dotnet test --project tests/ReactiveUI.Tests/ReactiveUI.Tests.csproj -- --treenode-filter "/*/*/MyClassName/*"

# All tests in namespace
dotnet test --project tests/ReactiveUI.Tests/ReactiveUI.Tests.csproj -- --treenode-filter "/*/MyNamespace/*/*"

# Filter by property (e.g., Category)
dotnet test --solution reactiveui.slnx -- --treenode-filter "/*/*/*/*[Category=Integration]"
```

See: [https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-test?tabs=dotnet-test-with-mtp](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-test?tabs=dotnet-test-with-mtp)
TUnit flags reference: [https://tunit.dev/docs/reference/command-line-flags](https://tunit.dev/docs/reference/command-line-flags)

---

## Key Configuration Files

* `src/global.json` β€” sets `"Microsoft.Testing.Platform"` runner
* `src/testconfig.json` β€” test execution settings (parallel false, coverage format, etc.)
* `src/Directory.Build.props` β€” repository-wide build configuration (incl. `TestingPlatformDotnetTestSupport`)
* `.github/copilot-instructions.md` β€” may exist, but should defer to this `agent.md`

---

## Architecture Overview

ReactiveUI is a cross-platform MVVM framework built on Rx.NET and functional reactive programming principles.

### Core Library (`src/ReactiveUI/`)

* `ReactiveObject/` β€” reactive `INotifyPropertyChanged` base
* `ReactiveCommand/` β€” observable command pipelines
* `Activation/` β€” view/viewmodel activation lifecycle
* `Bindings/` β€” one-way/two-way binding infrastructure
* `Expression/` β€” expression tree analysis for observation (`WhenAnyValue`)
* `Routing/` β€” navigation/routing
* `Interactions/` β€” request/response patterns
* `Builder/` β€” DI and service registration patterns

### Platform Extensions

Examples:

* `ReactiveUI.Wpf/`, `ReactiveUI.WinUI/`, `ReactiveUI.Maui/`, `ReactiveUI.AndroidX/`,
  `ReactiveUI.Blazor/`, `ReactiveUI.Winforms/`, `ReactiveUI.Testing/`, etc.

### Scheduler Abstraction

* Prefer `RxSchedulers` (AOT-friendly, avoids reflection/AOT attribute propagation)
* Use `RxApp` only when required (e.g., unit test scheduler detection)

See `docs/RxSchedulers.md`.

---

## AOT Guidance (Critical)

This repository targets net8.0+ and supports AOT/trimming scenarios.

### Primary Rule: Avoid Reflection Paths

Prefer strongly-typed and source-generator-friendly approaches. Avoid reflection-heavy patterns that require trimming/AOT attributes.

### Attributes: Use Only If Necessary

* Avoid introducing DAC/RDC/RUC attributes unless required.
* If an attribute is required, apply it directly (no `#if NET6_0_OR_GREATER` guards). Polyfills are available.

Example (only when truly needed):

```csharp
private static object CreateInstance(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
    Type type)
{
    return Activator.CreateInstance(type)!;
}
```

### Suppressions: Last Resort Only β€” Never Without Human Approval

**Do NOT add any suppression without explicit human approval.** This covers `[SuppressMessage]`, `[UnconditionalSuppressMessage]`, `#pragma warning`, and `.editorconfig` severity changes. A suppression is a true last resort, used only when a warning genuinely cannot be resolved by fixing the code without harming the design.

Before suppressing anything:

1. **Fix the underlying issue first.** Most analyzer warnings indicate a real fix. For example, `S3398` ("method should be moved") means *move the method into the type that uses it* β€” never suppress it. `SA****` (StyleCop) must always be fixed, never suppressed.
2. **If you believe a suppression is genuinely unavoidable, stop and ask the human.** Present the specific analyzer ID, why it cannot be fixed in code, and the proposed justification. Wait for explicit approval.
3. **Only after approval**, apply it with minimal scope, the specific ID, and a clear `Justification`.

---

## Code Style & Quality Requirements

**CRITICAL:** Follow ReactiveUI contribution guidelines:
[https://www.reactiveui.net/contribute/index.html](https://www.reactiveui.net/contribute/index.html)

### Enforced Tooling

* `.editorconfig` formatting/naming conventions
* StyleCop analyzers (build fails on violations)
* Roslynator analyzers
* Analysis level: latest
* Warnings treated as errors (notably nullable and CS4014)
* **Public APIs require XML documentation**, including protected methods on public types.

### C# Style Rules (High-level)

* Allman braces
* 4 spaces, no tabs
* Explicit visibility
* Private/internal fields: `_camelCase`, `readonly` where possible, `static readonly` order
* File-scoped namespaces preferred; using directives outside namespace and sorted
* Use C# keywords (`int`, `string`) rather than BCL types
* Prefer modern C# features where appropriate (nullable, pattern matching, switch expressions, records, init, target-typed new, etc.)
* Use `nameof()` over string literals
* Avoid `this.` unless necessary
* Use `var` when it improves readability

If a specific file already follows a local style, adhere to existing file conventions.

---

## Zero Pragma Policy (Critical)

**No `#pragma warning disable`** in production code.

* **StyleCop warnings (SA****) must be fixed**, never suppressed.
* **No analyzer warning (CA****, S**** Sonar, RCS**** Roslynator, IL**** trimming/AOT, etc.) may be suppressed without explicit human approval** β€” see "Suppressions: Last Resort Only" above. Fix the code first; if a suppression seems unavoidable, stop and ask.

Example:

```csharp
// WRONG
#pragma warning disable CA1062
public void MyMethod(object parameter)
{
    parameter.ToString();
}
#pragma warning restore CA1062

// CORRECT
public void MyMethod(object parameter)
{
    ArgumentNullException.ThrowIfNull(parameter);
    parameter.ToString();
}

// LAST RESORT ONLY
[SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods",
    Justification = "TUnit guarantees non-null parameters from data sources.")]
public async Task MyTest(IConverter converter, int expectedValue)
{
    var result = converter.GetValue();
    await Assert.That(result).IsEqualTo(expectedValue);
}
```

---

## Testing Guidelines

* Use TUnit + Microsoft Testing Platform
* Write unit tests for new features and bug fixes
* Prefer existing patterns in:

  * `src/tests/ReactiveUI.Tests/`
  * `src/tests/ReactiveUI.AOTTests/`
* Use `ReactiveUI.Testing` utilities for reactive code

---

## Common Development Patterns

### ViewModel Skeleton

```csharp
public class SampleViewModel : ReactiveObject
{
    private string? _name;
    private readonly ObservableAsPropertyHelper<bool> _isValid;

    public SampleViewModel()
    {
        _isValid = this.WhenAnyValue(x => x.Name)
            .Select(name => !string.IsNullOrWhiteSpace(name))
            .ToProperty(this, nameof(IsValid));

        SubmitCommand = ReactiveCommand.CreateFromTask(
            ExecuteSubmit,
            this.WhenAnyValue(x => x.IsValid));
    }

    public string? Name
    {
        get => _name;
        set => this.RaiseAndSetIfChanged(ref _name, value);
    }

    public bool IsValid => _isValid.Value;

    public ReactiveCommand<Unit, Unit> SubmitCommand { get; }

    private async Task ExecuteSubmit(CancellationToken cancellationToken)
    {
        // Implementation
    }
}
```

### RxSchedulers (Preferred)

```csharp
public IObservable<string> GetData()
{
    return Observable.Return("data")
        .ObserveOn(RxSchedulers.MainThreadScheduler);
}
```

### WhenAnyValue

```csharp
this.WhenAnyValue(
        x => x.FirstName,
        x => x.LastName,
        (first, last) => $"{first} {last}")
    .Subscribe(fullName => { /* handle */ });

this.WhenAnyValue(x => x.IsLoading)
    .Where(isLoading => !isLoading)
    .Subscribe(_ => { /* handle */ });
```

### ObservableAsPropertyHelper

```csharp
private readonly ObservableAsPropertyHelper<decimal> _total;
public decimal Total => _total.Value;

_total = this.WhenAnyValue(
        x => x.Quantity,
        x => x.Price,
        (qty, price) => qty * price)
    .ToProperty(this, nameof(Total));
```

---

## What to Avoid

* Reflection-heavy implementations in core paths
* Expression trees in hot paths without caching
* Platform-specific code in `src/ReactiveUI/` core library
* Breaking public APIs without proper versioning and documentation