# Manual Integration

Most apps should use `LivePanels.Canvas`, a toolkit preset, and
`canvas_runtime/1`.

Use manual integration APIs when you are building a bridge package, custom
render loop, manual protocol surface, durable layout store, or clustered shared
backend.

Manual integration should still preserve the main boundary: LivePanels owns
runtime behavior; the application owns product UI and product data.

## Decide What Belongs In LivePanels

Before extending the integration, classify the behavior:

| Behavior | Usually belongs |
|---|---|
| item placement, resize, focus, visibility, z-order | LivePanels canvas runtime |
| graph ports, connector validation, connector state | LivePanels graph runtime |
| committed vector items and vector item edits | LivePanels drawing runtime |
| route authorization, workflow rules, domain records | application |
| toolbar layout, menus, palettes, inspectors | application UI |
| reusable integration with another UI framework | bridge package |

If the behavior changes canvas state, prefer a public command helper. If the
behavior is product-specific meaning, keep it in the application and call LivePanels
only for canvas mutations.

## Mount Runtime Manually

Use `LivePanels.Canvas.mount_runtime/4` when the `use LivePanels.Canvas` macro
is not the right fit:

```elixir
defmodule MyAppWeb.BoardLive do
  use MyAppWeb, :live_view
  use LivePanels.Toolkit.Canvas

  @impl true
  def mount(params, session, socket) do
    LivePanels.Canvas.mount_runtime(
      [pubsub: MyApp.PubSub],
      params,
      session,
      socket
    )
  end
end
```

This keeps the same runtime config and command behavior while giving the application
direct lifecycle ownership.

## Render Runtime-Created Items

Runtime-created items use the same app-owned HEEx frame as source-owned items.
Declare an id-less item slot and branch on the item type or app props you store
in item state:

```heex
<.canvas_runtime {canvas_runtime_attrs(assigns)}>
  <:item :let={context}>
    <section class="card-item">
      <header {drag_handle_attrs()}>{context.item.label}</header>
      <p>{LivePanels.Item.prop(context.item, :body, "")}</p>
      <.resize_handles :if={context.view.resizable?} />
    </section>
  </:item>
</.canvas_runtime>
```

Server code creates those runtime items with direct payloads:

```elixir
socket =
  LivePanels.Canvas.Item.add!(
    socket,
    "report",
    id: "report-#{System.unique_integer([:positive])}",
    x: 120,
    y: 80,
    w: 640,
    h: 480,
    props: %{title: "Report"}
  )
```

## Build Manual Protocol Attrs

Prefer named helpers such as `add_item_attrs/2`, `remove_item_attrs/2`, graph
attrs, and drawing attrs.

Use `LivePanels.Toolkit.Integration.ProtocolAttrs` only when a bridge or custom application
surface needs lower-level command envelopes:

```heex
<button {LivePanels.Toolkit.Integration.ProtocolAttrs.command_attrs(:select_all)}>
  Select all
</button>

<button {LivePanels.Toolkit.Integration.ProtocolAttrs.item_command_attrs(:remove_item, item)}>
  Remove
</button>
```

Manual protocol attrs are public, but they are easier to misuse than named
helpers. Keep them close to integration code.

## Build Manual Render Loops

The ordinary render path is:

```heex
<.canvas_runtime {canvas_runtime_attrs(assigns)}>
  <:item :let={context}>
    ...
  </:item>
</.canvas_runtime>
```

Use `LivePanels.Toolkit.Integration.Render` when you intentionally take over item ordering,
DOM ids, z-index, frame styles, or vector item rendering:

```elixir
items =
  LivePanels.Toolkit.Integration.Render.items_for_render(
    LivePanels.Canvas.Read.items(socket),
    LivePanels.Canvas.Read.z_order(socket)
  )
```

```heex
<LivePanels.Toolkit.Integration.Render.canvas_item_frame
  item={context.item}
  view={context.view}
  client_id={@livepanels.client_id}
>
  ...
</LivePanels.Toolkit.Integration.Render.canvas_item_frame>
```

When you need to render the frame element yourself, use
`canvas_item_frame_attrs/4` for the same runtime-facing attributes:

```heex
<div
  {LivePanels.Toolkit.Integration.Render.canvas_item_frame_attrs(
    context.item,
    context.view,
    @livepanels.client_id,
    class: "app-frame"
  )}
>
  ...
</div>
```

Manual render loops must preserve the DOM attrs and layer structure required by
the browser runtime. If you only need custom chrome for runtime-owned items, use
an id-less `:item` renderer instead.

## Declare Browser Runtime Features

The canonical `canvas_runtime/1` component emits feature tokens from configured
extensions. Manual render loops or custom hook setup must keep that public
configuration explicit so graph, drawing, presence, keyboard, and marquee
controllers mount only for canvases that use them:

```heex
<.canvas_runtime
  {canvas_runtime_attrs(assigns,
    runtime_features: [:keyboard, :selection, :graph]
  )}
/>
```

The JavaScript hook accepts the same feature vocabulary:

```javascript
const LivePanelsCanvas = createLivePanelsCanvasHook({
  features: { graph: true, drawing: false, presence: false }
})
```

## Use Read-Side State

Use `LivePanels.Canvas.Read.state/1` only when lower-level helpers need the
canonical state:

```elixir
state = LivePanels.Canvas.Read.state(socket)
connectors = LivePanels.Graph.connectors(state)
```

Prefer narrower read helpers when possible:

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

Connector rendering is part of `canvas_runtime/1` when the graph extension is
configured. Read-side helpers are for inspectors and application decisions.
Mutations should still go through commands.

## Implement Durable Layout Stores

Use `LivePanels.Canvas.LayoutStore.Ecto` when layouts must survive process and
server restarts:

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

Persist the layout payload unchanged. It is a versioned runtime codec, not a
app-authored item list. Implement `LivePanels.Canvas.LayoutStore` directly only
when the application uses a non-Ecto database or external cache.

## Build Shared Backends

Use `LivePanels.Canvas.Shared.Local` for one-node shared canvases. Use
`LivePanels.Canvas.Shared.Horde` or a custom `LivePanels.Canvas.Shared.Adapter` when a
clustered deployment needs distributed coordinator lookup and supervision.

Changing the backend should not change command semantics. If it does, the
backend is doing product work that belongs in the application.

## Keep Manual Code Honest

Manual integration should still avoid:

- private runtime or compositor modules;
- browser controller internals;
- duplicate item, graph, drawing, history, or layout stores;
- DOM measurement as the canonical source of canvas state;
- application code that bypasses policy and command validation.

When in doubt, build an application helper around the public API instead of
reaching into internals.
