# Two-Tier Reactivity (Effects vs Hooks)

The core Config System and its `configSymbol` holding zone provide an incredibly robust engine for local, imperative state mutations. But a true platform needs a broader awareness. 

Frameworks often struggle because they try to solve two fundamentally different problems with the same tool:
1. **Local, imperative side-effects:** ("When I click this specific button, change its CSS class.")
2. **Global, derived state:** ("When the user's currency preference changes, update the total price calculation across 5 different views.")

If you try to solve global state with local hooks, you end up writing manual chains: A updates B, which updates C. This is brittle and difficult to trace. Neo.mjs solves this by providing a "Two-Tier" reactivity architecture, tailored for these distinct use cases: Push-Based Hooks and Pull-Based Effects.

## Tier 1: Push-Based (Lifecycle Hooks)

This is the classic, localized reactivity model powered by the dynamically generated **getters and setters** (the `trailing_underscore_` convention) created during `Neo.setupClass`.

It is **push-based** because a change to a property explicitly *pushes* an update through a predefined sequence of methods (`beforeSet` -> `afterSet`).

### When to use it
* Component-level state management.
* Validating input data before it is applied (`beforeSet`).
* Triggering direct, surgical DOM updates or side-effects when a specific property changes.

```javascript readonly
class MyButton extends Component {
    static config = {
        // Reactive config
        isActive_: false
    }

    // Push-based side effect
    afterSetIsActive(value, oldValue) {
        if (oldValue !== undefined) {
            // Imperatively update the VDOM based on the new value
            this.updateCls('active', value);
        }
    }
}
```

## Tier 2: Pull-Based (Effects & Dependency Tracking)

For complex, cross-component state, we need the engine to be intelligent enough to track dependencies automatically.

Neo.mjs provides a **pull-based** dependency tracking system via `src/core/Effect.mjs` and `src/core/EffectManager.mjs`.

### How it works
Instead of explicitly defining *when* something should update (like an `afterSet` hook), you define *what* it depends on via a function. The `EffectManager` automatically watches this function execute and records any reactive config accessed during that execution. When any of those tracked configs change in the future, the effect is automatically queued to re-run.

### Observing Other Instances
Because `Effects` track *any* accessed config, they inherently break the boundaries of a single instance. You can create an `Effect` that listens to configs on entirely different components or state managers.

This is the exact mechanism that powers two of Neo's most advanced features:

#### 1. Functional Components
When you use functional components (`Neo.functional.component.Base`), the engine automatically wraps your `createVdom()` method inside an `Effect`. If your `createVdom` accesses a reactive config on the component (or any other instance), the effect tracks it. When that config changes, the effect automatically re-runs `createVdom()` and diffs the output, giving you a modern, declarative rendering loop without manual hooks.

#### 2. The State Provider
The `Neo.state.Provider` is the ultimate realization of this system. It uses `createHierarchicalDataProxy.mjs` to wrap its internal data object. When an `Effect` accesses `data.user.firstName`, the proxy intercepts the read and registers the underlying config with the `EffectManager`.

This allows you to write clean formulas or component bindings that automatically update when distant data changes, without ever wiring up an event listener manually:

```javascript readonly
import Effect from './core/Effect.mjs';

// The effect automatically registers dependencies during its first run.
// If state.a or state.b changes later, this function re-runs automatically.
let myEffect = Neo.create(Effect, {
    fn: () => {
        // This function will re-run automatically whenever 
        // the state provider's 'a' or 'b' values change.
        sum = stateProvider.getData('a') + stateProvider.getData('b');
    }
});
```

### The Synergy of the Two Tiers (The Bridge)

The true magic of the Neo.mjs engine is that these two systems are perfectly integrated. 

Since v10, every push-based reactive config generated by `Neo.setupClass` (Tier 1) is internally backed by a `core.Config` instance. **This is the missing link that connects the two tiers.** Because of this architectural bridge, the `EffectManager` (Tier 2) can literally observe **ANY** reactive instance config natively—or even standalone custom `core.Config` instances. 

You can create an Effect that re-runs when a component's `text` changes, seamlessly bridging local component state with global dependency tracking. The `Neo.state.Provider` heavily leverages this concept: it converts every deeply nested property path (e.g., `user.address.city`) into a discrete `core.Config` instance internally, allowing the `createHierarchicalDataProxy` to intercept reads and pipe them directly into the active Effect.

Furthermore, batch updates via `set()` (utilizing the `configSymbol`) are wrapped in `EffectManager.pause()` and `EffectManager.resume()`. This guarantees that if multiple tracked configs change during a batch operation, the pull-based dependency graph is not re-evaluated until the entire batch is complete and internally consistent.

You can use explicit hooks for precise component rendering, and automated Effects for complex business logic and functional rendering loops, allowing for optimal performance and clean architecture.

We now have an instance that is compiled, locally reactive, and globally aware. It seems ready to go. But in Neo's multi-threaded architecture, synchronous readiness is an illusion. To truly join the application, the instance needs an asynchronous lifecycle.