# Save Layouts, History, And Clipboard

LivePanels keeps recoverable canvas behavior on the same state model as normal
item interaction.

That means moving an item, reconnecting graph connectors, committing a vector item,
copying a selection, undoing a change, and restoring a layout all operate on
canvas state instead of separate app-maintained models.

## What A Layout Contains

A saved layout is a versioned runtime payload.

It can include:

- item ids, types, placement, z-order, visibility, parent/attachment fields;
- persistent item props and allowed item options;
- capability snapshots needed to restore runtime behavior;
- vector state for committed vector items;
- graph connectors between persistent items;
- a schema header describing the layout format.

It does not include every display detail. Browser-only previews, temporary
loading state, open menus, unsaved form fields, and product records remain
outside the layout payload.

Store your domain data in your app. Store recoverable canvas arrangement in the
LivePanels layout.

## Configure Layout Persistence

The default `LivePanels.Canvas.LayoutStore.Registry` store is ETS-backed. It is
useful for development and single-node experiments, but it is not durable
across server restarts.

For durable persistence in an Ecto-backed Phoenix app, define a layout store
with `LivePanels.Canvas.LayoutStore.Ecto`:

```elixir
defmodule MyApp.CanvasLayouts do
  use LivePanels.Canvas.LayoutStore.Ecto,
    repo: MyApp.Repo,
    schema: MyApp.LivePanelsCanvasLayout
end
```

Wire it into the canvas:

```elixir
use LivePanels.Canvas,
  pubsub: MyApp.PubSub,
  session: [layout_store: MyApp.CanvasLayouts]
```

`load/1` runs during mount before content-bearing items render. `save/2` runs
after accepted mutations that affect recoverable layout state.

Keep both callbacks synchronous and fast. A failed save should return
`{:error, reason}` rather than crashing the canvas.
Implement `LivePanels.Canvas.LayoutStore` directly when the application uses a
non-Ecto database or external cache.

## Save Accepted State, Not Pointer Previews

Persistence follows accepted commands.

During a drag or resize, the browser may preview many frames. The layout store
should not receive every pointer frame. It receives the accepted item placement
after the command path validates and applies the change.

The same rule applies to drawing and graph work:

- a drawing draft stays in the browser until it is committed as a vector item;
- a graph connection draft stays transient until a valid connector is created;
- undo and redo work against accepted state changes;
- shared mode broadcasts accepted state, not private browser previews.

## Use History

Render history controls with toolkit attrs:

```heex
<button {undo_attrs()}>Undo</button>
<button {redo_attrs()}>Redo</button>
```

Or call the owner module:

```elixir
socket = LivePanels.Canvas.History.undo!(socket)
socket = LivePanels.Canvas.History.redo!(socket)
```

History is actor-scoped. In a shared canvas, one participant's undo stack does
not have to be another participant's undo stack.

Applications can inspect history projection to enable controls:

```elixir
history = LivePanels.Canvas.Read.history(socket)

can_undo? = LivePanels.Canvas.History.can_undo?(history)
can_redo? = LivePanels.Canvas.History.can_redo?(history)
```

Transactions create one logical history entry for multi-step work.

## Use Clipboard

Render clipboard controls:

```heex
<button {copy_selection_attrs()}>Copy</button>
<button {paste_selection_attrs()}>Paste</button>
```

Or call the owner module:

```elixir
socket = LivePanels.Canvas.Clipboard.copy!(socket)
socket = LivePanels.Canvas.Clipboard.paste!(socket, offset: %{x: 32, y: 32})
```

Clipboard is actor-scoped. It copies selected canvas items and graph connectors whose
endpoints are inside the copied item set. Vector items are items, so they can
participate in copy and paste like other persistent items.

Use the read side to drive UI:

```elixir
clipboard = LivePanels.Canvas.Read.clipboard(socket)
has_clipboard? = not LivePanels.Canvas.Clipboard.empty?(clipboard)
```

## Persistent And Ephemeral Items

Some items should restore with the canvas. Others are temporary runtime chrome.

Use persistent items for content, graph nodes, vector items, and user-created
workspace objects.

Use ephemeral or viewport items for local HUDs, transient overlays, menus, and
other UI that should be rebuilt by the application instead of restored from layout.

The practical question is: if the browser reloads or a shared room recovers,
should this thing come back as part of the canvas? If yes, make it persistent
canvas state. If no, keep it as application UI or ephemeral runtime state.

## Shared Canvases

Shared canvases use the same layout payload, but the coordination path changes.

Configure durable layout persistence in the shared backend or canvas config so
the shared coordinator can recover room state. Do not maintain separate
per-browser layout stores for the same shared canvas id.

The shared-canvas guide covers setup. The shared operations guide covers
production backend and recovery concerns.

## What LivePanels Owns

LivePanels owns the versioned layout payload, accepted mutation history,
actor-scoped clipboard projection, and restore timing.

Your application owns the durable store configuration, product records referenced
by item props, and the visible controls for save, restore, undo, redo, copy, and
paste.
