Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,4 @@ It may include TODOs on WIP.
- [Migration to GC in Components](./wep-2026-03-28-gc-in-components.md)
- [Redesign String and Array APIs](./wep-2026-03-29-redesign-string-array-api.md)
- [WebIDL Binding Generator (`wado-from-idl`)](./wep-2026-04-01-tide.md)
- [Reactive Signals](./wep-2026-04-04-reactive-signals.md)
178 changes: 1 addition & 177 deletions docs/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -3524,183 +3524,7 @@ test "add negative numbers" {

## Reactive System

Wado has built-in reactive signals (called "signals" in other frameworks like SolidJS, Svelte 5). The compiler analyzes dependencies at compile-time and generates efficient update code.

### The `reactive` Keyword

**Source** values are mutable reactive state:

```wado
let reactive mut count = 0;

count = 5; // Mutation triggers updates
count += 1; // Also triggers updates
```

**Derived** values are computed from other reactive values:

```wado
let reactive doubled = || count * 2;
let reactive quadrupled = || doubled * 2;

// Reading derived values
let x = doubled; // Returns current computed value
```

Derived values are recomputed when their dependencies change. The compiler builds a dependency graph and updates values in topological order.

### The `observe` Function

The `observe` function (from `core:reactive`) executes side effects when reactive dependencies change. Dependencies are automatically tracked—any reactive value read within the closure becomes a dependency.

```wado
use {observe} from "core:reactive";

let reactive mut count = 0;
let reactive doubled = || count * 2;

observe(|| {
println(`Count is now: {count}`);
// Dependencies: count
});

observe(|| {
println(`Doubled is now: {doubled}`);
// Dependencies: doubled (and transitively, count)
});
```

**Cleanup:**

Return a cleanup function to run when the observation is disposed or before re-running:

```wado
observe(|| {
let subscription = external_api.subscribe(`event-{count}`);
println(`Subscribed to event-{count}`);

return || {
subscription.unsubscribe();
println(`Cleaned up subscription for event-{count}`);
};
});
```

The cleanup function runs:

- Before the effect re-runs (when dependencies change)
- When the enclosing scope ends
- When the component unmounts (in UI contexts)

**Manual disposal:**

```wado
let dispose = observe(|| {
println(`Count: {count}`);
});

// Later, stop observing
dispose();
```

### Reactive References

Reactive values can be passed by reference to functions:

```wado
fn increment(counter: &reactive mut i32) {
*counter += 1; // Triggers updates in caller's scope
}

let reactive mut count = 0;
let reactive doubled = || count * 2;

increment(&reactive count); // count becomes 1, doubled becomes 2
```

### Execution Semantics

Reactive behavior differs between execution contexts:

#### CLI World (Synchronous)

In CLI programs, reactive updates are **synchronous and immediate**:

```wado
use {observe} from "core:reactive";

let reactive mut count = 0;
let reactive doubled = || count * 2;

observe(|| {
println(`doubled = {doubled}`);
});

count = 5;
// observe() callback runs immediately here, before next line
// Output: "doubled = 10"

println("after mutation");
// Output: "after mutation"
```

- Updates propagate immediately when a source is mutated
- observe() callbacks run synchronously before execution continues
- Observations live for the duration of their enclosing scope

#### Event-Looped World (Browser/GUI)

In event-driven contexts, reactive updates are triggered by **external events**:

```wado
use {observe} from "core:reactive";

fn Counter() -> Element with Dom {
let reactive mut count = 0;
let reactive doubled = || count * 2;

observe(|| {
println(`Count changed to {count}`);
});

return <div>
<p>{doubled}</p>
<button onclick={|_| count += 1}>+1</button>
</div>;
}
```

- Updates are triggered by events (clicks, timers, network responses)
- Multiple mutations within a single event handler may be **batched**
- observe() callbacks and UI bindings persist for the component's lifetime
- The event loop keeps the program alive to receive future events

#### Comparison

| Aspect | CLI | Event-looped |
| ------------------ | ------------------------- | ------------------------------- |
| Trigger | Direct assignment in code | External events |
| Propagation | Synchronous, immediate | May be batched per event |
| observe() lifetime | Enclosing scope duration | Component/subscription lifetime |
| Primary use case | Computed dependencies | UI binding, subscriptions |

### JSX Integration

Reactive values integrate seamlessly with JSX:

```wado
fn Counter() -> Element with Dom {
let reactive mut count = 0;

return <button onclick={|_| count += 1}>
{count}
</button>;
}
```

The compiler tracks that `{count}` depends on the reactive value and generates code to update only that text node when `count` changes—no virtual DOM diffing required.

`Reactive` is built into the language; no `with` declaration required.
Wado has built-in reactive signals with compile-time dependency analysis and a push-pull update algorithm. See [WEP: Reactive Signals](./wep-2026-04-04-reactive-signals.md) for the full design. **Not yet implemented.**

---

Expand Down
Loading
Loading