# `StarView`
[🔗](https://github.com/Neophen/star_view/blob/v0.7.0/lib/star_view.ex#L1)

Elixir SDK helpers for Datastar.

The facade functions in this module delegate to small protocol modules while
keeping a pipeline-friendly Plug API:

    conn
    |> StarView.start()
    |> StarView.patch_signals(%{count: 1})
    |> StarView.patch_elements(~s(<div id="count">1</div>))

## Controller Behaviour

Use `use StarView` in your `AppWeb.star_view/0` macro, then
implement callbacks with `@impl StarView`:

### Lifecycle

1. `mount/2` — Sets up initial signals and assigns for the page load.
2. `render/1` — Renders the HEEx template. The generated `Layout.app/1`
   wrapper uses `init_signals/1` to emit the `data-signals` attribute for
   the initial client state.
3. `handle_event/3` — Called by `StarView.Dispatch` when a Datastar
   action fires. The dispatcher starts the SSE response before this callback,
   so `signal/3` patches browser signals immediately.

### Example

    @impl StarView
    def mount(conn, _params) do
      conn
      |> signal(:count, 0)
      |> signal(:step, 1)
    end

    @impl StarView
    def render(assigns) do
      ~H"""
      <Layout.app conn={@conn}>
        <button data-on:click={post("increment")}>+</button>
        <span data-text="$count">{@count}</span>
      </Layout.app>
      """
    end

    @impl StarView
    def handle_event("increment", signals, conn) do
      conn
      |> signal(:count, Map.get(signals, "count", 0) + 1)
    end

# `handle_event`
*optional* 

```elixir
@callback handle_event(String.t(), map(), Plug.Conn.t()) :: Plug.Conn.t()
```

# `mount`

```elixir
@callback mount(Plug.Conn.t(), map()) :: Plug.Conn.t()
```

# `render`

```elixir
@callback render(map()) :: term()
```

# `__using__`
*macro* 

Provides Phoenix controller helpers.

Use `use StarView` in your `AppWeb.star_view/0` macro
instead of `use StarView.Controller` directly:

    def star_view do
      quote do
        use Phoenix.Controller, formats: [:html, :json]
        use StarView
        use Phoenix.Component

        use Gettext, backend: MyAppWeb.Gettext

        import Phoenix.Component, except: [assign: 3]
        import Plug.Conn

        alias MyAppWeb.Components.StarView.Layout

        plug :put_root_layout, false

        unquote(verified_routes())
      end
    end

# `check_connection`

```elixir
@spec check_connection(Plug.Conn.t()) ::
  {:ok, Plug.Conn.t()} | {:error, Plug.Conn.t()}
```

Checks whether a chunked SSE connection still accepts writes.

# `console_log`

```elixir
@spec console_log(Plug.Conn.t(), term(), keyword()) :: Plug.Conn.t()
```

Logs a value to the browser console.

# `execute_script`

```elixir
@spec execute_script(Plug.Conn.t(), String.t(), keyword()) :: Plug.Conn.t()
```

Executes JavaScript in the browser by appending a script element.

# `fetch_signals`

Fetches object signals and returns Plug's updated conn; see `StarView.Signals.fetch/2`.

# `invalidate`

```elixir
@spec invalidate(Plug.Conn.t()) :: Plug.Conn.t()
```

Requests one coalesced `:render` from the running stream; see `StarView.Stream.invalidate/1`.

# `patch_elements`

```elixir
@spec patch_elements(Plug.Conn.t(), iodata() | tuple() | nil, keyword()) ::
  Plug.Conn.t()
```

Patches one or more complete HTML elements into the DOM.

# `patch_signals`

```elixir
@spec patch_signals(Plug.Conn.t(), map(), keyword()) :: Plug.Conn.t()
```

Patches client-side Datastar signals using RFC 7386 JSON Merge Patch semantics.

# `patch_signals_raw`

```elixir
@spec patch_signals_raw(Plug.Conn.t(), String.t(), keyword()) :: Plug.Conn.t()
```

Patches client-side Datastar signals from a pre-encoded JSON string.

# `read_signals`

```elixir
@spec read_signals(Plug.Conn.t()) :: map()
```

Reads Datastar signals from a Plug connection.

Returns a signal map. Raises `StarView.Signals.ReadError` when the
payload cannot be decoded. Plugs that need `{:ok, map()} | {:error, term()}`
should call `StarView.Signals.read/1` instead.

# `read_signals!`

```elixir
@spec read_signals!(Plug.Conn.t()) :: map()
```

Reads Datastar signals from a Plug connection, raising on invalid JSON.

# `redirect`

```elixir
@spec redirect(Plug.Conn.t(), String.t(), keyword()) :: Plug.Conn.t()
```

Redirects the browser by executing a tiny client-side script.

# `remove_elements`

```elixir
@spec remove_elements(Plug.Conn.t(), String.t(), keyword()) :: Plug.Conn.t()
```

Removes elements from the DOM by CSS selector.

# `remove_signals`

```elixir
@spec remove_signals(Plug.Conn.t(), String.t() | [String.t()], keyword()) ::
  Plug.Conn.t()
```

Removes signals by setting one or more dot-notated signal paths to `null`.

# `send`

```elixir
@spec send(Plug.Conn.t(), String.t(), [String.t()] | String.t(), keyword()) ::
  Plug.Conn.t()
```

Sends a raw Datastar SSE event and returns the updated connection.

# `start`

```elixir
@spec start(Plug.Conn.t()) :: Plug.Conn.t()
```

Starts a Server-Sent Events response on a Plug connection.

# `start_stream`

```elixir
@spec start_stream(Plug.Conn.t(), term()) :: Plug.Conn.t()
```

Starts an SSE stream with per-tab deduplication.

Requires `StarView.StreamRegistry` in your supervision tree
and a `tabId` signal in your root layout. See
`StarView.StreamRegistry` for setup.

# `start_stream`

Starts a keyed SSE stream with configurable signal input options.
