# Use Commands And Policy

Every canvas mutation should enter LivePanels as a command.

A command is a request to change or inspect runtime state. It can come from a
application function call, a browser attr, item content, graph interaction, drawing
gesture, shared participant, or transaction.

LivePanels uses one command path so authorization, validation, reducers,
history, persistence, shared coordination, and projection stay consistent.

## Use Owner Modules From Server Code

Call the module that owns the thing you are changing:

```elixir
socket = LivePanels.Canvas.Item.move_to!(socket, "note-1", {120, 80})
socket = LivePanels.Canvas.Item.resize_to!(socket, "note-1", {420, 300})
socket = LivePanels.Canvas.Item.merge_props!(socket, "note-1", %{status: "review"})
socket = LivePanels.Canvas.Selection.select!(socket, "note-1")
socket = LivePanels.Canvas.History.undo!(socket)
```

Graph and drawing commands use their own owner modules:

```elixir
socket =
  LivePanels.Graph.connect!(socket,
    from: {"extract-1", "output"},
    to: {"transform-1", "input"}
  )

socket =
  LivePanels.Drawing.vector_item!(socket, :rectangle, %{
    space: :world,
    anchor: :top_left,
    x: 120,
    y: 80,
    w: 240,
    h: 160
  })
```

These calls do not patch private assigns. They submit commands to the runtime.

## Use Toolkit Attrs From Vector Itemup

For browser-triggered commands, use toolkit attrs:

```heex
<button {add_item_attrs(:note)}>New note</button>
<button {remove_item_attrs(context)}>Close</button>
<button {layout_attrs(:grid)}>Layout</button>
<button {undo_attrs()}>Undo</button>
```

The helper emits the event name and payload fields the browser/runtime protocol
expects. Your application still owns the element, label, CSS, and placement.

Use graph attrs for ports and connectors. Use drawing attrs for vector item style, erase
targets, and point handles.

## Commands Are Checked

A command can be rejected before it becomes state.

Examples:

- the target item does not exist;
- an add-item payload is malformed;
- an item capability is missing;
- a graph port direction or type is incompatible;
- a drawing payload is malformed;
- the actor is a viewer;
- application policy rejects the request.

Use the bare command helper when application code needs to react immediately:

```elixir
case LivePanels.Canvas.Item.move_by(socket, "note-1", {24, 0}) do
  {:ok, socket, _meta} ->
    {:noreply, socket}

  {:error, reason, socket, _meta} ->
    {:noreply, put_flash(socket, :error, inspect(reason))}
end
```

Every command owner module returns result tuples from its mutating commands:
item commands, props, selection, layout, history, clipboard, viewport, graph,
and drawing. Use bang variants in LiveView callbacks that should receive the
next socket or raise on rejection. Browser-triggered commands still record the
latest result in `LivePanels.Canvas.Read.last_command/1`.

Rejected commands should be visible enough to debug. They should not leave the
canvas half-mutated.

## Add Application Policy

Use `LivePanels.Canvas.Policy` when product data should decide whether a canvas
mutation may happen.

```elixir
defmodule MyApp.CanvasPolicy do
  @behaviour LivePanels.Canvas.Policy

  @impl true
  def authorize(%LivePanels.Canvas.Policy.Request{action: :remove_item} = request) do
    if MyApp.Workflows.item_locked?(request.canvas_id, request.item_ids, request.actor) do
      {:error, :active_workflow_reference}
    else
      :ok
    end
  end

  def authorize(_request), do: :ok
end
```

Configure the policy on the canvas:

```elixir
use LivePanels.Canvas,
  pubsub: MyApp.PubSub,
  policy: MyApp.CanvasPolicy
```

The policy receives a request with the action, command, actor, canvas id, target
item ids, and runtime context. Return `:ok` to allow the command,
`{:ok, metadata}` to allow it with decision metadata, or `{:error, reason}` /
`{:halt, reason}` to reject it.

Use policy for product rules that can answer in-process. Use item capabilities
for runtime rules such as whether an item can be removed, resized, connected,
or placed.

## Coordinate Application Writes

When canvas state mirrors application records, run the application write in the
application callback or context module, then issue LivePanels commands
explicitly after that write succeeds.

```elixir
def handle_event("move_card", %{"id" => id, "dx" => dx, "dy" => dy}, socket) do
  delta = {String.to_integer(dx), String.to_integer(dy)}

  case MyApp.Boards.move_card(id, delta) do
    {:ok, card} ->
      socket =
        LivePanels.Canvas.transaction!(socket, fn tx ->
          tx
          |> LivePanels.Canvas.Item.move_by!(id, delta)
          |> LivePanels.Canvas.Item.merge_props!(id, %{
            card_id: card.id,
            card_version: card.lock_version
          })
        end)

      {:noreply, socket}

    {:error, reason} ->
      {:noreply, assign(socket, :canvas_error, reason)}
  end
end
```

Policy remains available for acceptance rules that must apply to every command
source:

```elixir
use LivePanels.Canvas,
  pubsub: MyApp.PubSub,
  policy: MyApp.CanvasPolicy
```

In shared mode policy runs inside the canonical coordinator command path, so all
participants observe the same accepted or rejected result. LivePanels validates
the command before invoking policy. Policy callbacks run behind a timeout
boundary because they execute in the reducer and shared coordinator command path;
a timeout rejects the LivePanels command and kills the policy process.

## Use Transactions For Multi-Step Work

Use `LivePanels.Canvas.transaction/2` when several changes should be applied as
one coordinated operation:

```elixir
socket =
  LivePanels.Canvas.transaction!(socket, fn tx ->
    tx
    |> LivePanels.Canvas.Item.add!(:note, id: "note-1")
    |> LivePanels.Canvas.Item.place!("note-1", %{
      space: :world,
      anchor: :top_left,
      x: 120,
      y: 80,
      w: 360,
      h: 240
    })
    |> LivePanels.Canvas.Selection.select!("note-1")
  end)
```

Use `transaction/2` when a composed operation needs one explicit success
or failure result:

```elixir
case LivePanels.Canvas.transaction(socket, fn tx ->
       LivePanels.Canvas.Item.add!(tx, :note, id: "note-1")
     end) do
  {:ok, socket, _meta} ->
    {:noreply, socket}

  {:error, reason, socket, _meta} ->
    {:noreply, put_flash(socket, :error, inspect(reason))}
end
```

Transactions are useful for imports, templates, graph edits, and server actions
that combine item props, placement, selection, and connectors.

## Keep Browser Intent Separate From State

The browser can hold transient intent while a gesture is underway.

For example, while dragging an item the browser knows the pointer position and
preview frame. That is not accepted canvas state yet. The final placement
becomes a command on release.

The same idea applies to graph connection drafts and drawing drafts. The user
gets responsive feedback, but the server accepts the durable change.

## Command Sources

Common command sources are:

- application APIs such as `LivePanels.Canvas.Item.add/3`;
- browser attrs such as `add_item_attrs/2`;
- item content helpers such as `LivePanels.Item.Content.LiveView.Commands.remove/1`;
- graph attrs such as `begin_connector_attrs/3` and `complete_connector_attrs/3`;
- drawing attrs such as `update_style_attrs/3`;
- shared participants through the shared coordinator;
- transactions composed by the application.

They should all mean the same thing after acceptance: a command updated canvas
state and the runtime projected the new state to the connected clients.

## What Not To Do

Avoid these patterns:

- replacing LivePanels assigns with your own item list;
- changing item placement only in DOM or JavaScript;
- hand-writing command payload attrs when a toolkit helper exists;
- reaching into private compositor/runtime modules from application code;
- maintaining a second graph, drawing, or layout state model next to the
  canvas unless it is domain data owned by your app.

Use public command helpers so local mode, shared mode, persistence, history,
graph, drawing, and browser projection stay on the same contract.
