# `LivePanels.Canvas.LayoutStore.Registry`
[🔗](https://github.com/livepanels/livepanels/blob/v0.1.0/lib/livepanels/canvas/layout_store/registry.ex#L1)

ETS-backed registry mapping canvas_id -> canvas metadata.

Canvases are independently scoped. A user can have multiple canvases.
Each canvas has its own item arrangement, its own PubSub topic,
and is completely independent of whatever content an application
chooses to render inside it.

This module is responsible for persisting canvas layout only: which items
exist, where they are, and the layout codec fields needed to reopen them.
What runs inside content-bearing items is tracked elsewhere.

## LayoutStore

This module implements `LivePanels.Canvas.LayoutStore` and is the default
persistence backend used by `LivePanels.Canvas`. Layout data is stored in
an ETS table and survives LiveView process restarts, but is lost when the
server node restarts.

For durable cross-restart persistence, use
`LivePanels.Canvas.LayoutStore.Ecto` or another durable
`LivePanels.Canvas.LayoutStore` and pass your module via the
`session: [layout_store: ...]` option on `use LivePanels.Canvas`.

## TTL configuration

`LivePanels.Canvas.LayoutStore.Registry` is a single process-level layout store. `use
LivePanels.Canvas` selects the store module, but it does not configure this
registry's TTL on a per-canvas basis.

To change the default 24-hour TTL or the cleanup cadence, start the registry
with explicit opts in your supervision tree:

    children = [
      {LivePanels.Canvas.LayoutStore.Registry,
       ttl_ms: :timer.hours(1),
       cleanup_interval_ms: :timer.minutes(10)}
    ]

The package child list uses the bare `LivePanels.Canvas.LayoutStore.Registry` child by default,
which makes custom TTL policy an explicit supervision decision.

# `canvas_entry`

```elixir
@type canvas_entry() :: %{
  id: String.t(),
  created_at: DateTime.t(),
  updated_at: DateTime.t(),
  updated_at_ms: non_neg_integer(),
  layout: [map()]
}
```

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `delete`

```elixir
@spec delete(String.t()) :: :ok
```

Deletes the stored layout metadata for a canvas id.

# `get_or_create`

```elixir
@spec get_or_create(String.t()) :: {:ok, canvas_entry()}
```

Create or retrieve a canvas entry by id.

# `load`

```elixir
@spec load(String.t()) :: {:ok, [map()]} | :not_found
```

Loads the most recently saved versioned layout for a canvas.

Returns `{:ok, layout}` if a non-empty layout has been saved, or
`:not_found` if no layout exists yet for this canvas.

Reads directly from ETS (no GenServer hop) so it is safe to call from
any process, including during LiveView `mount/3`.

# `prune_stale`

```elixir
@spec prune_stale(non_neg_integer()) :: non_neg_integer()
```

Prunes canvases that have not been updated within `older_than_ms`.

Returns the number of deleted canvas entries.

# `save`

```elixir
@spec save(String.t(), [map()]) :: :ok
```

Persists the versioned layout for a canvas.

`layout` must be a list of `LivePanels.Canvas.LayoutStore.layout_entry()`
maps.

---

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