# `Rho.Dyn.Seed`
[🔗](https://github.com/GenericJam/rho/blob/v0.1.0/lib/rho/dyn/seed.ex#L1)

The seed: the Dyn sources an install starts from, which the host ships
(Operator's default front: a welcome screen, a component library, the
front's settings). The host embeds them at build time
(`Application.app_dir/2` can't resolve priv/ on the device) and hands them
to `start/2`.

`start/2` installs it in the background after the launch's Dyn step
(`Rho.Dyn.seed/3`): compiling a big seed whole takes about half a minute
on a Moto G 2021. The seed becomes a generation like any other, so its
screens are the agent's to change, and reverting to that generation
restores the defaults.

**A newer seed on a phone that has one** (an app update that changed
the seed). The install records each seed file's sha256 as it shipped
(`Rho.Dyn.Store.seed_files/1`). When this build's seed differs, it is
installed again, merged three ways with what runs now (`merge/3`): a
seed file the user (or the agent) never changed is replaced by the new
one (or removed, when the new seed dropped it); a new seed file is added
unless the user already has a file at that path; a file the user
changed, deleted or wrote stays exactly as it is. So user edits are never
lost, at the price that an edited seed file doesn't get the update
(reverting to the seed's generation, or asking the agent, does). The
merged generation goes through the whole check, compile and selftest; if
it fails (an edited file relies on something the new seed changed),
nothing changes and that seed is recorded as rejected, so it isn't
retried every launch; the next app update with a different seed tries
again. An install that recorded nothing (an older app's) is taken to have
shipped the `:legacy` digests, if the host has them (Operator 1.0's);
the seed generation's own sources can't stand in for them, since that
install merged the user's files into it.

# `digests`

```elixir
@spec digests(%{required(String.t()) =&gt; String.t()}) :: %{
  required(String.t()) =&gt; String.t()
}
```

Each file's sha256 (hex), as `needed/3` and the seed's record compare them.

# `merge`

```elixir
@spec merge(
  %{required(String.t()) =&gt; String.t()},
  %{required(String.t()) =&gt; String.t()},
  %{
    required(String.t()) =&gt; String.t()
  }
) :: %{required(String.t()) =&gt; String.t()}
```

The sources to install: `seed` over `current` (what runs now), given
`shipped` (path => sha256 of the seed files as the installed seed shipped
them; `%{}` for a first install). See the moduledoc for the rules.

# `needed`

```elixir
@spec needed(Path.t(), %{required(String.t()) =&gt; String.t()}, %{
  required(String.t()) =&gt; String.t()
}) ::
  :install | {:update, %{required(String.t()) =&gt; String.t()}} | :none
```

What the Dyn store at `dir` needs for a seed whose files have `digests`:
`:install` (no seed yet), `{:update, shipped}` (an older seed is
installed; `shipped` its files' digests as they shipped, `legacy` when it
recorded none) or `:none` (this seed, or this seed was rejected there).

# `running?`

```elixir
@spec running?() :: boolean()
```

Is the seed being installed right now?

# `start`

```elixir
@spec start(%{required(String.t()) =&gt; String.t()}, keyword()) ::
  {:ok, pid()} | :ignore
```

Installs (or updates) the seed `sources` in a process of its own
(registered under this module's name) unless it's current, or can't be
now (safe mode, a proposal pending: the next launch tries again). When it
ends, `:notify` runs (the host's front redraws). Options: `:keeper`
(`Rho.Dyn.Keeper`), `:notify` (a 0-arity function, default none),
`:rationale`, `:legacy` (see `needed/3`).

---

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