# `AshUi.Reload`
[🔗](https://github.com/hyperbob-labs/ash_ui/blob/main/lib/ash_ui/reload.ex#L1)

The O(1) dispatch reload contract — the ONE canonical policy for what a
bridge reloads after a dispatch. Every transport bridge calls
`after_dispatch/4`; none owns a private copy of the policy, so a new
target cannot drift from it.

The contract:

  * Data reloads are exactly (a) the notification ECHO — the action's own
    synchronous write notifications, scoped to the view dependency they
    touched — and (b) the view roots whose declared read depends on a root
    attribute the action changed (`dependent_roots_for_attributes/2`).
  * Compiler event paths (a binding's each source and render deps) are
    RENDER scope only — they address the differential re-render and never
    justify a data read: entry rows did not change unless a notification
    says so.
  * A `:root`-scoped echo is the page row's own write notification. The
    dispatch result is already the authoritative fresh root row, so it
    adds no data reload (the historical drift: one bridge dropped it, the
    other answered with a full view reload).
  * Unaffected dependencies keep their already-loaded values
    (`AshUi.preserve_loaded_values/2`).

Dependency narrowing derives from declarations, never from code:

  * A declarative relationship depends on its `source_attribute` and any
    root attribute referenced through `parent(...)` in its filter.
  * A `manual` relationship's read is opaque; it conservatively depends on
    every attribute unless the declaration names its dependency set:
    `manual {MyRel, depends_on: [:sort_by, :q]}` (an empty list is a valid
    declaration: the read depends on no root attribute).
  * An expression calculation depends on the root attributes its expression
    references (through other calculations transitively) and is invalidated
    by data changes on any relationship path it references.
  * A module calculation declares its dependencies through `load/3`; when
    that cannot be read it conservatively depends on everything.
  * An aggregate depends on its relationship path's first edge exactly like
    a declarative relationship, and is invalidated by data changes on that
    path.

# `dep`

```elixir
@type dep() :: :all | %{attributes: MapSet.t(atom()), relationships: MapSet.t(atom())}
```

# `after_dispatch`

```elixir
@spec after_dispatch(struct(), struct(), :root | [term()], keyword()) ::
  {:ok, struct()} | {:error, term()}
```

The canonical post-dispatch refresh. `current` is the rendered page record,
`updated` the same-row action result, `echo` the dispatch's notification
echo (`:root` or a list of `:root`/relationship-path entries). Returns the
record to render from, with unaffected loaded values preserved and only the
affected dependencies re-read.

# `changed_attributes`

```elixir
@spec changed_attributes(struct(), struct()) :: [atom()]
```

Root attributes whose value differs between the rendered record and the
action result — the attribute-diff half of the reload contract.

# `dependent_roots_for_attributes`

```elixir
@spec dependent_roots_for_attributes(module(), [atom()]) :: [atom()]
```

The top-level view dependencies whose value can change when the given root
attributes change, per the declaration-derived dependency index.

# `dependent_roots_for_paths`

```elixir
@spec dependent_roots_for_paths(module(), [[atom()] | atom()]) :: [atom()]
```

The top-level view roots invalidated by a DATA change on the given
relationship paths: calculations and aggregates whose declared dependencies
traverse a changed path. This is what keeps a derived root (a flattened
rows calculation over `:messages`, an aggregate over `:albums`) fresh when
the change arrives as a notification rather than a root-attribute write.

# `derived_reads`

```elixir
@spec derived_reads(module(), atom()) :: [[atom()] | {:resource, module()}]
```

What a derived view field READS, from the same declaration-derived
dependency derivation the reload index uses: every relationship path a
calculation or aggregate traverses (through `load/3`, DSL `load:`,
expression references, aggregate paths and nested calculations or
aggregates), and `{:resource, module}` for an aggregate over an unrelated
resource. This is the subscription side of "whatever a page reads, it
subscribes to": Ash loads these dependencies as hidden internal loads, so
they never appear on the rendered record for a relationship walk to see.

An undeclarable dependency (a raising `load/3`, an expression Ash cannot
walk) contributes nothing beyond what was already derived.

# `same_row?`

```elixir
@spec same_row?(struct(), struct()) :: boolean()
```

True when the dispatch result is the same session row the page renders —
the case where the action result IS the fresh page state and the narrowed
refresh applies. Any other row needs the page re-read.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
