From 277749de1a87d0d8f6d91bae7c8fddd4cce9be3d Mon Sep 17 00:00:00 2001 From: Glenn Watson <5834289+glennawatson@users.noreply.github.com> Date: Sun, 27 Sep 2026 15:54:26 +1000 Subject: [PATCH 1/9] docs(claude): add the soft-tone Material 3 diagram style --- CLAUDE.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 02d63da6..95895096 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -114,6 +114,41 @@ The model is [`ReactiveUI.Binding.SourceGenerators/README.md`](https://github.co the problem first, then a numbered walkthrough of the first use, then reference sections. A reader who stops after the walkthrough can already do the thing. +### Diagrams: soft-tone Material 3 + +The site renders ```` ```mermaid ```` fences (NuStreamDocs.Mermaid, Mermaid 10). Every diagram uses the same palette, +taken from Material 3 container roles and seeded from the site's ReactiveUI blue (`#319af3`). Fills are light tones with +dark text, so a diagram reads in both the light and dark site themes. Copy the `init` line and the `classDef` lines +exactly. Do not invent colours. + +```mermaid +%%{init: {"theme": "base", "themeVariables": {"fontFamily": "Roboto, Helvetica, Arial, sans-serif", "fontSize": "15px", "primaryColor": "#DCE9FF", "primaryBorderColor": "#6C8EC4", "primaryTextColor": "#0B2447", "secondaryColor": "#E3F2E8", "secondaryBorderColor": "#7FA88C", "secondaryTextColor": "#12301C", "tertiaryColor": "#F3E5F5", "tertiaryBorderColor": "#A98BB0", "tertiaryTextColor": "#2E1437", "lineColor": "#7B8699", "textColor": "#1B1F27", "noteBkgColor": "#FFF4D6", "noteBorderColor": "#C9A94F", "noteTextColor": "#3A2A00", "actorBkg": "#DCE9FF", "actorBorder": "#6C8EC4", "actorTextColor": "#0B2447", "signalColor": "#7B8699", "signalTextColor": "#1B1F27", "labelBoxBkgColor": "#F1F3F8", "labelBoxBorderColor": "#A7AEBB", "edgeLabelBackground": "#F7F9FC", "clusterBkg": "#F7F9FC", "clusterBorder": "#C9D1DE"}}}%% +flowchart LR + classDef view fill:#DCE9FF,stroke:#6C8EC4,color:#0B2447 + classDef vm fill:#E3F2E8,stroke:#7FA88C,color:#12301C + classDef model fill:#F3E5F5,stroke:#A98BB0,color:#2E1437 + classDef warn fill:#FDE7E4,stroke:#C98A82,color:#410E0B + classDef neutral fill:#F1F3F8,stroke:#A7AEBB,color:#1B1F27 + View(["View"]):::view -- "binds to" --> VM(["View model"]):::vm + VM -- "reads and writes" --> Model(["Model"]):::model +``` + +| Class | Tone | Use for | +| --- | --- | --- | +| `view` | soft blue (primary container) | views, controls, anything the user sees | +| `vm` | soft green (secondary container) | view models, commands, reactive objects | +| `model` | soft lavender (tertiary container) | models, stores, services, data | +| `warn` | soft rose (error container) | errors, exceptions, failure paths | +| `neutral` | soft grey (surface variant) | infrastructure: sequencers, the builder, the resolver | + +- Use a diagram only where it shows something faster than words: data flowing between view, view model and model; a + command's life cycle; a navigation stack; activation and deactivation; where work runs; suspension states. Most + pages need at most two. +- Use rounded shapes (`(["text"])` or `("text")`) and `flowchart LR` unless the flow is a stack or a timeline. Use + `sequenceDiagram` for "who calls whom, in what order" and `stateDiagram-v2` for life cycles. +- Keep to about eight nodes. Labels are plain words a grade 8 reader follows, in the page's own terms. +- Put each diagram after the paragraph that introduces it, and follow it with one sentence saying what to notice. + ## In Flight: Per-Operator Pages For ReactiveUI.Primitives The site is to carry one page per operator family for `ReactiveUI.Primitives`, with an example for every From d7e8a1389dfbb751b09beefb9f2c30067f95aa40 Mon Sep 17 00:00:00 2001 From: Glenn Watson <5834289+glennawatson@users.noreply.github.com> Date: Sun, 27 Sep 2026 16:08:17 +1000 Subject: [PATCH 2/9] docs(reactiveui): rewrite the Avalonia binding page for ReactiveUI.Avalonia --- .../handbook/data-binding/avalonia.md | 115 +++++++++--------- 1 file changed, 57 insertions(+), 58 deletions(-) diff --git a/docs/documentation/reactiveui/handbook/data-binding/avalonia.md b/docs/documentation/reactiveui/handbook/data-binding/avalonia.md index d5fc98f8..b556263d 100644 --- a/docs/documentation/reactiveui/handbook/data-binding/avalonia.md +++ b/docs/documentation/reactiveui/handbook/data-binding/avalonia.md @@ -1,72 +1,71 @@ --- Order: 3 --- -# Data Binding in AvaloniaUI with ReactiveUI +# Data Binding in Avalonia -> **Note** First of all, ensure you install the `Avalonia.ReactiveUI` package and *add a call* to `UseReactiveUI()` to your `AppBuilder` — that's super important. Note, that `Avalonia.ReactiveUI` package is supported by AvaloniaUI team, so if anything goes wrong, head over to [AvaloniaUI Gitter](https://gitter.im/AvaloniaUI/Avalonia). Also see [Avalonia.ReactiveUI Docs](https://docs.avaloniaui.net/guides/deep-dives/reactiveui). +An Avalonia view binds to its view model with the same `Bind`, `OneWayBind` and `BindCommand` calls as any other +platform. The Avalonia pieces, such as the view base classes and the start-up call, ship in the `ReactiveUI.Avalonia` +package. That package lives in its own repository, [reactiveui/ReactiveUI.Avalonia](https://github.com/reactiveui/ReactiveUI.Avalonia), +so the code on this page is quoted from its +[example app](https://github.com/reactiveui/ReactiveUI.Avalonia/tree/main/src/examples/ReactiveUI.Avalonia.Example). +[Installing for Avalonia](../../getting-started/installation/avalonia.md) lists the packages. -For [WhenActivated](../when-activated.md) to work, you need to use custom base classes from `Avalonia.ReactiveUI` package, such as `ReactiveWindow` or `ReactiveUserControl`. Of course, you can also implement the `IViewFor` interface by hand in your class, but ensure to store the `ViewModel` inside an `AvaloniaProperty`. If you wish to add activation support to your view models, then implement the `IActivatableViewModel` interface. A view model implementing `IActivatableViewModel` might look like the one below: +## Bind a view in three steps -```cs -public class ViewModel : ReactiveObject, IActivatableViewModel -{ - public ViewModelActivator Activator { get; } = new ViewModelActivator(); +1. **Start ReactiveUI with the app.** Call `UseReactiveUI` on Avalonia's `AppBuilder`. It registers the main-thread + sequencer, activation and binding support. `RegisterReactiveUIViewsFromEntryAssembly` registers each view in your + app, so view location can find it. - public ViewModel() - { - this.WhenActivated(disposables => - { - /* Handle activation */ - Disposable - .Create(() => { /* Handle deactivation */ }) - .DisposeWith(disposables); - }); - } -} -``` + ```cs + public static AppBuilder BuildAvaloniaApp() => AppBuilder + .Configure() + .UsePlatformDetect() + .UseReactiveUI(rxui => + { + // Optional: add custom registration here via rxui.WithRegistration(...) + }) + .RegisterReactiveUIViewsFromEntryAssembly(); + ``` -Remember to place a call to `WhenActivated` to the code-behind of your view, otherwise your view model's `WhenActivated` block won't get called. +2. **Derive the view from a ReactiveUI base class.** `ReactiveWindow` and `ReactiveUserControl` + implement `IViewFor`. They store the view model in an Avalonia property, so XAML bindings see it too. + Avalonia generates a field for each control that has an `x:Name` in the markup, so the view can name its controls + directly. -```cs -// This represents the View.xaml.cs code-behind file. -public partial class View : ReactiveWindow -{ - public View() - { - // If you put a WhenActivated block here, your activatable view model - // will also support activation, otherwise it won't. - this.WhenActivated(disposables => { /* Handle interactions etc. */ }); - AvaloniaXamlLoader.Load(this); - } -} -``` +3. **Bind inside `WhenActivated`.** [WhenActivated](../when-activated.md) runs its block when the view joins the + visual tree and disposes everything the block added when the view leaves it. Add each binding to the block's + `MultipleDisposable`, so no binding outlives the view. -```xml - - - - -``` + ```cs + public sealed partial class CommandLabView : ReactiveUserControl + { + public CommandLabView() + { + InitializeComponent(); + _ = this.WhenActivated(BindView); + } -The `WhenActivated` block is called when the `AttachedToVisualTree` event is published, and the disposable is disposed of when the `DetachedFromVisualTree` event is published. The behavior is the same for both the views and the view models. Unfortunately, Avalonia won't generate strongly typed `x:Name` references to controls, so the only way for now is to use the `FindControl` method that will find a control by the name specified in XAML, or to use `{Binding Path}` syntax. + private void BindView(ReactiveUI.Primitives.Disposables.MultipleDisposable disposables) + { + disposables.Add(this.Bind(ViewModel, static viewModel => viewModel.WorkItemText, static view => view.WorkItemTextBox.Text)); + disposables.Add(this.OneWayBind(ViewModel, static viewModel => viewModel.LastResult, static view => view.LastResultText.Text)); + disposables.Add(this.BindCommand(ViewModel, static viewModel => viewModel.RunWork, static view => view.RunWorkButton)); + } + } + ``` -> **Note** The `FindControl` method shouldn't be used inside an expression. Instead, create a custom property which calls the `FindControl` method, or store the control in a variable. See an example of how to use ReactiveUI type-safe bindings with AvaloniaUI. +`WorkItemTextBox`, `LastResultText` and `RunWorkButton` are the `x:Name` values in the view's `.axaml` file. The +lambdas are `static` because they capture nothing. See +[Dispose your subscriptions](../../guidelines/framework/dispose-your-subscriptions.md) for why every binding goes +into the activation block. -```cs -public partial class View : ReactiveWindow -{ - // Assume the Button control has the Name="ExampleButton" attribute defined in XAML. - public Button ExampleButton => this.FindControl