Skip to main content

CHANGELOG.md

# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

### Changed

### Deprecated

### Removed

### Fixed

### Security

## [0.4.0] - 2026-05-08

### Added

- `:key` attribute on component tags inside `:for` loops in `~F` templates. Components are now
  identified by their key rather than their position in the list, giving stable fiber identity
  across reorders without any manual VNode construction:

  ```elixir
  # before — manual {:keyed_list, ...} VNode
  def render(%{items: items}) do
    keyed = Enum.map(items, fn item ->
      {item.id, {:component, MyItem, %{item: item}, item.id}}
    end)
    {:keyed_list, keyed}
  end

  # after — declarative :key attribute
  def render(%{items: items}) do
    ~F"""
    <MyItem :for={item <- items} :key={item.id} item={item} />
    """
  end
  ```

- `Filament.Experimental.Hooks.use_event_ref/1` — registers an event handler and returns a
  stable wire ref string (e.g., `"filament:root.MyComponent[0]:0"`) that a JS hook can pass
  directly to `pushEvent`, routing the event to the specific fiber without session IDs or
  process-dictionary workarounds:

  ```elixir
  import Filament.Experimental.Hooks

  def render(props) do
    submit_ref = use_event_ref(fn %{"text" => t} -> ... end)
    ~F"""
    <textarea phx-hook="MyHook" data-ref={submit_ref} />
    """
  end
  ```

  Opt in with `import Filament.Experimental.Hooks`. The API is experimental and may change.

### Removed

- `{:keyed_list, ...}` VNode type and all related renderer/validation logic. Use `:for` + `:key`
  on component tags in `~F` templates instead (see above).

## [0.3.0] - 2026-05-07

### Added

- `on_key` attribute for zero-config keyboard event handling. Add it to any
  element to bind a window-level keydown handler; the handler receives the key
  string and a `%Filament.KeyModifiers{}` struct with `ctrl`, `shift`, `alt`,
  and `meta` boolean fields — no `phx-key`, no custom JS hook required:

  ```elixir
  ~F"""
  <div on_key={fn "Escape", _ -> close() end}>
  </div>
  """
  ```

  Pattern match on the key string to filter; use `_` to ignore modifiers you
  don't care about.

- Bang variants for all `Filament.Test` helpers: `mount!/2`, `click!/2`,
  `submit!/3`, `change!/3`, `blur!/2`, `key_down!/2`, `key_down!/3`. Each
  unwraps `{:ok, view}` and raises on error, enabling pipeline-style test
  composition:

  ```elixir
  mount!(Counter, %{initial: 0})
  |> click!("button")
  |> click!("button")
  |> assert_text("2")
  ```

- `Filament.Test.change/3` — triggers a `phx-change` event on a form element.
- `Filament.Test.blur/2` — triggers a `phx-blur` event on an element.
- `Filament.Test.key_down/3` — element-scoped `phx-keydown` (3-arity, alongside
  the existing 2-arity window-scoped `key_down/2`).

### Fixed

- Fixed event handler index collision between compile-time `on_*` handlers and
  runtime `register_event_handler` calls. Previously, handlers registered inside
  `{for … do}` loops could silently overwrite `on_*` handlers in the same
  component.

## [0.2.1] - 2026-05-07

### Changed

- The project license has changed from Apache-2.0 to MIT.

### Fixed

- `~F` formatter now preserves `<script>` block content verbatim. Previously,
  JavaScript inside colocated `<script :type={ColocatedHook}>` blocks was
  re-indented as if it were HTML, corrupting indentation-sensitive code.

## [0.2.0] - 2026-05-06

### Added

- `use_observable/2` now accepts a positional projection fn as its second argument. The fn
  receives `:disconnected` when the server is unavailable, or the raw server state otherwise,
  and its return value becomes the hook's result:

  ```elixir
  count = use_observable(CartServer, fn
    :disconnected -> 0
    state -> Cart.State.item_count(state)
  end)
  ```

- `static_subscribe` option on `Filament.LiveView` (default: `true`) controls whether the
  HTTP render pass subscribes to observables. Set to `false` on a live view to prevent
  double-counting presence or other mount side effects on page reload — subscriptions are
  then established only once the WebSocket session connects.

- Support for `<script :type={Phoenix.LiveView.ColocatedHook}>` in `~F` templates.
  Modules using `use Filament.Component` now correctly register colocated JS hooks
  alongside those from `use Phoenix.Component`.

### Changed

- Projection fns now run **client-side at render time** rather than server-side at broadcast
  time. This means a projection fn can close over local component state (filters, selections,
  etc.) so changing that local state correctly re-projects without a new server broadcast.
  The server sends raw state; change-or-bust comparison is `new_raw_state !== last_raw_state`
  per subscriber.

- `handle_subscribe/3``handle_subscribe/2`: the `request` argument has been removed.
  Update your `Observable.GenServer` implementations:

  ```elixir
  # before
  def handle_subscribe(_request, _subscriber, state), do: {:ok, state, state}

  # after
  def handle_subscribe(_subscriber, state), do: {:ok, state, state}
  ```

- `Observable.subscribe/3``Observable.subscribe/2`: the `request` argument has been removed.
- `Observable.remove_projection/5``Observable.remove_projection/4`: the `request` argument
  has been removed.
- `Subscriber` struct: `request` and `projections` fields replaced by `proj_keys` and `last_raw`.

- `~F` templates no longer accept `@foo` assign syntax — use bare lexical variables from
  destructured function arguments instead. `@foo` in a `~F` template now raises a compile
  error. `{if … do}`, `{for … do}`, `{else}`, and `{end}` are handled natively by the tag
  engine rather than via a regex preprocessing pass (no behaviour change for existing templates).

### Removed

- The `request` parameter has been removed from the entire observable stack
  (`handle_subscribe`, `Observable.subscribe`, `Observable.remove_projection`, `Subscriber`
  struct).

### Fixed

- Fixed `keyed_list` removal leaking observable projection keys, causing stale subscriptions
  when list items are removed.

## [0.1.0] - 2026-05-01

### Added
- Initial project scaffold
- Mix project structure with Elixir 1.17+ and OTP 26+ support
- GitHub Actions CI with matrix testing
- ExDoc configuration for documentation
- Basic supervision tree structure