nuri · git:20260828.965f05b · 2026-08-28 · sha256 ab997fa09836a1eb

nuri git:20260828.965f05bA

Immutable. This exact content is served forever at /api/v1/blob/ab997fa09836a1eb.

---
name: nuri
description: Build and maintain Nuri applications using platform-neutral components, ordered hooks, keyed reconciliation, and renderer-owned materialization.
---

# 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.

## 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.

## 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);
}

void AddItem()
{
    Update(current => current with
    {
        Items = current.Items.Append(newItem).ToArray()
    });
}
```

### Shared state: `Store<T>` + `useStore`

For state shared across components.

```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.

```csharp
useEffect(() =>
{
    var cts = new CancellationTokenSource();
    _ = LoadAsync(cts.Token);
    return () => cts.Cancel();
}, Array.Empty<object>());
```

## Component Decomposition

- 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.

```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);
}
```

## 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 multiples of 4: `4, 8, 12, 14, 16, 18, 20, 24, 32`.

### Font sizes

`11, 12, 13, 14, 15, 18, 20, 22, 26, 30`

### Corner radius

`8, 10, 12, 14, 16, 18`

### Centralized palette (recommended for larger samples)

```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";
}
```

## Sample Checklist

Before submitting a new sample, verify:

- [ ] `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`.

## Required References

- 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).