nuri ยท diff
git:20260828.965f05b to git:20260908.a7a31d5
67 added, 206 removed. Audit A to A.
---
name: nuri
- description: Build and maintain Nuri applications using platform-neutral components, ordered hooks, keyed reconciliation, and renderer-owned materialization.
+ description: Build C# desktop UI applications with Nuri, a React-style MVU framework that renders platform-neutral virtual elements into native WPF and Avalonia controls. Use when creating or editing Nuri components, hooks (useState, useEffect, useStore, useMemo), keyed lists, routing, or animations, when scaffolding a XAML-free pure-C# desktop app, or when debugging Nuri rendering, lifecycle, or diagnostics output.
---
# Nuri Application Skill
- Read [Getting Started](../../docs/guides/GETTING_STARTED.md) before creating a new Nuri application and [Hook Reference](../../docs/guides/HOOKS.md) before changing component state, effects, stores, navigation, or services.
+ Nuri components are plain C# classes. `Render()` returns platform-neutral `IElement` descriptions, and the renderer adapter materializes them into native WPF or Avalonia controls. No XAML, no ViewModels, no data binding.
+ ## Step 1: Choose a Renderer
+
+ | Situation | Renderer | Package | Read next |
+ |---|---|---|---|
+ | Windows-only app, or modernizing an existing WPF application | WPF | `Nuri.WPF` | [references/wpf.md](references/wpf.md) |
+ | Cross-platform app, or an existing Avalonia host application | Avalonia | `Nuri.Avalonia` | [references/avalonia.md](references/avalonia.md) |
+
+ Read only the reference file for the chosen renderer, then follow its startup pattern. Never mix renderer-specific APIs in one application, and never reference WPF or Avalonia types inside a component.
+
+ ## Step 2: Scaffold
+
+ With the `Nuri.Templates` NuGet package installed (`dotnet new install Nuri.Templates`):
+
+ ```powershell
+ dotnet new nuri.wpf -n MyApp
+ dotnet new nuri.avalonia -n MyApp
+ dotnet new nuri-component -n Todo # single component item template
+ ```
+
## Non-Negotiable Rules
- `Render()` returns platform-neutral `IElement` descriptions. Do not create WPF, Avalonia, or Duxel controls in Core components.
- Call ordered hooks consistently on every render. Do not put state, reducer, ref, latest, store, memo, effect, or navigation hooks behind conditional control flow.
- Use `.Key(...)` for stateful dynamic-list and route children.
- Put fast-changing state in the smallest component that displays it. Preserve parent ownership when siblings coordinate on the same state.
- Treat `useService<T>()` as an `IServiceProvider` lookup. Nuri does not own service registration, lifetime, or disposal; use `Store<T>` or `useEffect` for observable service state.
- - Keep renderer-specific APIs in renderer projects. Core must remain platform-neutral.
+ - Keep renderer-specific APIs in the application host, not in components.
## Component Structure Template
Every component follows this layout. Keep the order exactly as shown.
```csharp
using Nuri.UI.Controls;
using Nuri.UI.Dsl;
using Nuri.UI.Values;
namespace Sample.Components;
public sealed class MyComponent : Component
{
private static readonly Item[] InitialItems = { ... };
public override IElement Render()
{
// 1. Hooks (always in the same order)
var (state, setState) = useState(new MyState(...));
var stateRef = useLatest(state);
var derived = useMemo(() => Compute(state), state);
// 2. Blank line
// 3. Local functions (state mutators)
void Update(Func<MyState, MyState> change)
{
var next = change(stateRef.Current);
stateRef.Current = next;
setState(_ => next);
}
void DoSomething() { ... }
// 4. Blank line
// 5. Return the UI tree
return
Div(
Text("Title")
.FontSize(22)
.FontWeight(FontWeightValue.Bold),
Button("Action", DoSomething)
)
.Padding(24)
.Background("#0f172a");
}
// 6. Static helper methods for UI decomposition
private static IElement SubView(Item item) { ... }
// 7. Static pure functions
private static string[] Validate(MyState state) { ... }
}
// 8. Record types at the bottom of the file
internal sealed record MyState(string Draft, Item[] Items);
internal sealed record Item(string Id, string Text);
```
- ## Formatting Quick Reference
-
- Full rules: [FORMATTING.md](../../docs/guides/FORMATTING.md)
-
- ### Good
-
- ```csharp
- public override IElement Render()
- {
- var (count, setCount) = useState(0);
-
- return
- Div(
- Text("Counter")
- .FontSize(22)
- .FontWeight(FontWeightValue.Bold),
- Grid(
- Button("-", () => setCount(c => c - 1)).Column(0),
- Text(count.ToString()).Column(1),
- Button("+", () => setCount(c => c + 1)).Column(2)
- )
- .Columns(Pixels(60), Star, Pixels(60))
- )
- .Padding(24)
- .Background("#0f172a");
- }
- ```
-
- ### Bad
-
- ```csharp
- public override IElement Render()
- {
- var (count, setCount) = useState(0);
- return Div(Text("Counter").FontSize(22).FontWeight(FontWeightValue.Bold), Grid(Button("-", () => setCount(c => c - 1)).Column(0), Text(count.ToString()).Column(1), Button("+", () => setCount(c => c + 1)).Column(2)).Columns(Pixels(60), Star, Pixels(60))).Padding(24).Background("#0f172a");
- }
- ```
-
- ### Key rules
-
- - `return` on its own line; indent the expression by 4 spaces.
- - Container children (`Div`, `Grid`, `Column`, `Row`, `Stack`, `Panel`, `Scroll`) one per line.
- - Container fluent calls at the closing-paren indentation.
- - Control fluent calls indented 4 spaces under their receiver.
- - One blank line between hooks, local functions, and the return expression.
-
- ## State Management Patterns
-
- ### Local state: `useState`
-
- For simple component-local values.
-
- ```csharp
- var (count, setCount) = useState(0);
- var (name, setName) = useState("world");
- ```
-
- ### Stale-closure-safe updates: `useLatest` + `Update`
-
- When multiple state fields change together or when callbacks capture state, use `useLatest` with a local `Update` function.
-
- ```csharp
- var (state, setState) = useState(new MyState(...));
- var stateRef = useLatest(state);
-
- void Update(Func<MyState, MyState> change)
- {
- var next = change(stateRef.Current);
- stateRef.Current = next;
- setState(_ => next);
- }
+ ## Hook Quick Reference
- void AddItem()
- {
- Update(current => current with
- {
- Items = current.Items.Append(newItem).ToArray()
- });
- }
- ```
+ | Hook | Use |
+ |---|---|
+ | `useState<T>(initial)` | component-local state; the setter receives `Func<T, T>` |
+ | `useReducer<TState, TAction>(reducer, initial)` | complex state transitions |
+ | `useRef<T>(initial)` | mutable box that never triggers a render (drag, pan) |
+ | `useLatest<T>(value)` | stale-closure-safe latest value inside callbacks |
+ | `useMemo<T>(factory, deps...)` | derived data cached until dependencies change |
+ | `useEffect(effect, deps)` | async loading, subscriptions, timers; return a cleanup function |
+ | `useStore(store)` / `useStore(store, selector)` | state shared across components |
+ | `useService<T>()` | resolve from the externally configured `IServiceProvider` |
+ | `useNavigation(initialRoute)` | local route state; pair with `Router(...)` and `Route(...)` |
- ### Shared state: `Store<T>` + `useStore`
+ Setter forms: `setCount(current => current + 1)` for updates based on the existing value, `setCount(_ => 42)` for replacement.
- For state shared across components.
+ Shared state:
```csharp
internal static class UserStore
{
public static readonly Store<UserState> State = Store.Create(new UserState("Guest"));
}
public override IElement Render()
{
var user = useStore(UserStore.State, s => s);
return Text(user.Name);
}
```
- ### Derived data: `useMemo`
-
- For expensive computations that depend on state.
-
- ```csharp
- var visibleItems = useMemo(
- () => state.Items.Where(i => i.Matches(state.Filter)).ToArray(),
- state.Items, state.Filter);
- ```
-
- ### Transient mutable state: `useRef`
-
- For drag, pan, or other interaction state that should not trigger re-render.
-
- ```csharp
- var dragRef = useRef<DragState?>(null);
- ```
-
- ### Side effects: `useEffect`
-
- For async loading, subscriptions, timers. Always return a cleanup function when needed.
+ Effect with cleanup:
```csharp
useEffect(() =>
{
var cts = new CancellationTokenSource();
_ = LoadAsync(cts.Token);
return () => cts.Cancel();
}, Array.Empty<object>());
```
- ## Component Decomposition
+ ## Keys
- - Keep `Render()` under ~150 lines. Decompose into `private static IElement` helpers when it grows.
- - Keep helper method parameters under 7. Use a record parameter object when more are needed.
- - Extract repeated UI patterns into reusable static methods.
+ Use explicit keys for rows and components whose identity must survive reorder, filter, edit, or remove operations:
```csharp
- public override IElement Render()
- {
- var (state, setState) = useState(...);
-
- return
- Div(
- Header(state.ActiveCount),
- Composer(state.Draft, AddItem),
- NotesList(state.Items)
- )
- .Padding(32);
- }
-
- private static IElement Header(int activeCount)
- {
- return Text($"{activeCount} active")
- .FontSize(20)
- .FontWeight(FontWeightValue.Bold);
- }
+ Div(items.Select(item =>
+ (IElement)new TodoItemComponent(item).Key(item.Id)
+ ).ToArray());
```
- ## Visual Design Conventions
-
- Use these color palettes for consistency across samples.
-
- ### Dark theme
-
- | Role | Hex | Usage |
- |------|-----|-------|
- | Deep background | `#0B1120` / `#0F172A` | Root background |
- | Surface | `#111827` | Card, panel |
- | Surface alt | `#1E293B` | Secondary panel, sidebar |
- | Border | `#334155` | Panel borders |
- | Border subtle | `#475569` | Dividers |
- | Primary text | `#F8FAFC` | Headings |
- | Secondary text | `#94A3B8` | Muted text |
- | Tertiary text | `#CBD5E1` | Labels |
- | Muted text | `#64748B` | Hints, disabled |
- | Accent blue | `#2563EB` | Active buttons |
- | Accent blue dark | `#1D4ED8` | Button brush |
- | Selected bg | `#DBEAFE` | Selected items |
- | Success | `#047857` / `#86EFAC` | Positive state |
- | Error | `#BE123C` | Error state |
-
- ### Light theme
-
- | Role | Hex | Usage |
- |------|-----|-------|
- | Canvas | `#F3F4F6` / `#F8FAFC` | Page background |
- | Surface | `#FFFFFF` | Card background |
- | Primary text | `#111827` | Headings |
- | Secondary text | `#6B7280` | Body text |
- | Muted text | `#64748B` | Hints |
- | Border | `#E5E7EB` | Card borders |
- | Accent blue | `#2563EB` | Active/selected |
- | Selected bg | `#DBEAFE` | Selected row |
- | Error | `#BE123C` | Error state |
-
- ### Spacing scale
+ Use a stable, sibling-unique value. `Name` is only a compatibility fallback; new code should always use `.Key(...)`.
- Use multiples of 4: `4, 8, 12, 14, 16, 18, 20, 24, 32`.
+ ## Layout Essentials
- ### Font sizes
+ - `Grid(...)` with fluent `.Rows("Auto,*")` and `.Columns(240, Star)`; numeric values are pixels, `*` and `2*` are weighted.
+ - `Scroll(...)` is a single-content viewport; put vertical layout and spacing on its one child.
+ - `VStack(...)` / `HStack(...)` create vertical/horizontal stacks.
+ - Animate with `.Transition(ms, EasingValue.CubicInOut)` after the property setter that should animate.
- `11, 12, 13, 14, 15, 18, 20, 22, 26, 30`
+ ## Formatting Essentials
- ### Corner radius
+ - `return` on its own line; indent the expression by 4 spaces.
+ - Container children one per line; container fluent calls at closing-paren indentation.
+ - One blank line between hooks, local functions, and the return expression.
+ - Full rules and Good/Bad examples: [references/design.md](references/design.md).
- `8, 10, 12, 14, 16, 18`
+ ## Design Conventions
- ### Centralized palette (recommended for larger samples)
+ Use the palettes, spacing scale, and pre-finish checklist in [references/design.md](references/design.md). Do not invent arbitrary hex colors.
- ```csharp
- internal static class Palette
- {
- public const string Canvas = "#0F172A";
- public const string Surface = "#111827";
- public const string SurfaceAlt = "#1E293B";
- public const string Border = "#334155";
- public const string TextPrimary = "#F8FAFC";
- public const string TextSecondary = "#94A3B8";
- public const string TextMuted = "#64748B";
- public const string Accent = "#2563EB";
- public const string AccentDark = "#1D4ED8";
- public const string Error = "#BE123C";
- }
- ```
+ ## Debugging
- ## Sample Checklist
+ When runtime behavior is wrong (blank UI, stale values, duplicated state, performance), follow the self-diagnosis loop in [references/troubleshooting.md](references/troubleshooting.md) before changing code.
- Before submitting a new sample, verify:
+ ## Verification Before Finishing
- - [ ] `Render()` follows the component structure template (hooks, functions, return order).
- - [ ] `return` is on its own line with 4-space indentation.
- - [ ] Container children are one per line.
- - [ ] Hooks are called unconditionally in the same order on every render.
- - [ ] `.Key(...)` is used for dynamic list children.
- - [ ] Fast-changing state is in the smallest component that needs it.
- - [ ] `useLatest` + `Update` pattern is used for complex state mutations.
- - [ ] Colors come from the design conventions above (not arbitrary hex values).
- - [ ] `Render()` is under ~150 lines; large UIs are decomposed into static helpers.
- - [ ] Helper methods have 7 or fewer parameters.
- - [ ] Record types are at the bottom of the file.
- - [ ] File-scoped namespace is used.
- - [ ] The sample builds with `dotnet build Nuri.sln -c Release`.
+ 1. `dotnet build -c Release` passes with zero errors.
+ 2. Run the app and exercise the changed behavior.
+ 3. Check runtime diagnostics output for `DuplicateKey`, `UnsupportedProperty`, `UnsupportedEvent`, or unexpected `FullRebuild` entries.
- ## Required References
+ ## Full Documentation
- - Runtime changes: [Runtime Architecture](../../docs/architecture/RUNTIME_ARCHITECTURE.md), [Runtime Identity](../../docs/architecture/RUNTIME_IDENTITY.md), and [Lifecycle](../../docs/architecture/LIFECYCLE.md).
- - Renderer changes: [Renderer Contracts](../../docs/renderers/README.md).
- - Full repository instructions: [AGENTS.md](../../AGENTS.md).
+ - [Getting Started](https://github.com/lukewire129/Nuri/blob/main/docs/guides/GETTING_STARTED.md)
+ - [Hook Reference](https://github.com/lukewire129/Nuri/blob/main/docs/guides/HOOKS.md)
+ - [Formatting](https://github.com/lukewire129/Nuri/blob/main/docs/guides/FORMATTING.md)
+ - [YAML Styles](https://github.com/lukewire129/Nuri/blob/main/docs/guides/YAML_STYLES.md)
+ - Working on the Nuri repository itself: [AGENTS.md](https://github.com/lukewire129/Nuri/blob/main/AGENTS.md)