# `Minga.Events`
[🔗](https://github.com/jsmestad/minga/blob/main/lib/minga/events.ex#L1)

Internal event bus for cross-component notifications.

Wraps an Elixir `Registry` in `:duplicate` mode so multiple processes
can subscribe to the same topic and receive broadcasts without knowing
about each other. The Editor (or any event source) calls `broadcast/2`;
subscribers receive the payload in `Registry.dispatch/3` callbacks.

## Usage

    # In a subscriber (GenServer init, or any long-lived process):
    Minga.Events.subscribe(:buffer_saved)

    # In an event source:
    Minga.Events.broadcast(:buffer_saved, %Events.BufferEvent{buffer: buf, path: path})

Subscribers receive the event synchronously in the dispatch callback,
which runs in the broadcaster's process. For heavy work, subscribers
should send themselves a message and handle it asynchronously.

## Payload types

Each topic has a typed struct payload with `@enforce_keys`. This means
the compiler catches missing fields at construction time, and the type
checker flags wrong field types (e.g. atom instead of pid). Subscribers
pattern-match on the struct, so malformed payloads can't sneak through.

| Topic            | Payload struct    | Required fields              |
|------------------|-------------------|------------------------------|
| `:buffer_saved`   | `BufferEvent`        | `buffer: pid(), path: String.t()`              |
| `:buffer_opened`  | `BufferEvent`        | `buffer: pid(), path: String.t()`              |
| `:buffer_closed`  | `BufferClosedEvent`  | `buffer: pid(), path: String.t() | :scratch`  |
| `:buffer_changed` | `BufferChangedEvent` | `buffer: pid(), source: EditSource.t()`  |
| `:mode_changed`   | `ModeEvent`          | `old: atom(), new: atom()`        |
| `:git_status_changed` | `GitStatusEvent` | `git_root, entries, branch, ahead, behind` |
| `:diagnostics_updated` | `DiagnosticsUpdatedEvent` | `uri: String.t(), source: atom()` |
| `:lsp_status_changed` | `LspStatusEvent` | `name: atom(), status: atom(), uri: String.t() | nil` |
| `:project_rebuilt` | `ProjectRebuiltEvent` | `root: String.t()` |
| `:command_done`    | `CommandDoneEvent`    | `name: String.t(), exit_code: non_neg_integer()` |
| `:log_message`     | `LogMessageEvent`     | `text: String.t(), level: :info | :warning | :error` |
| `:face_overrides_changed` | `FaceOverridesChangedEvent` | `buffer: pid(), overrides: map()` |

## Why Registry?

`Registry` ships with OTP (no dependencies), supports pattern-based
dispatch, and has zero overhead for topics with no subscribers. It is
the same primitive that Phoenix.PubSub builds on, without the Phoenix
dependency. The wrapper module makes swapping to PubSub or `:pg`
a one-file change if distributed events are ever needed.

# `payload`

```elixir
@type payload() ::
  Minga.Events.BufferEvent.t()
  | Minga.Events.BufferClosedEvent.t()
  | Minga.Events.BufferChangedEvent.t()
  | Minga.Events.ModeEvent.t()
  | Minga.Events.ToolMissingEvent.t()
  | Minga.Events.ProjectRebuiltEvent.t()
  | Minga.Events.CommandDoneEvent.t()
  | Minga.Events.GitStatusEvent.t()
  | Minga.Events.DiagnosticsUpdatedEvent.t()
  | Minga.Events.LspStatusEvent.t()
  | Minga.Events.SupervisorRestartedEvent.t()
  | Minga.Events.LogMessageEvent.t()
  | Minga.Events.FaceOverridesChangedEvent.t()
  | MingaAgent.SessionManager.SessionStoppedEvent.t()
```

Typed event payloads. Each topic has a specific struct.

# `topic`

```elixir
@type topic() ::
  :buffer_saved
  | :buffer_opened
  | :buffer_closed
  | :buffer_changed
  | :content_replaced
  | :mode_changed
  | :git_status_changed
  | :diagnostics_updated
  | :lsp_status_changed
  | :tool_install_started
  | :tool_install_progress
  | :tool_install_complete
  | :tool_install_failed
  | :tool_uninstall_complete
  | :tool_missing
  | :project_rebuilt
  | :command_done
  | :supervisor_restarted
  | :log_message
  | :face_overrides_changed
  | :agent_session_stopped
  | :changeset_merged
  | :changeset_budget_exhausted
  | :load_user_themes
  | :buffer_fork_conflict
  | :extension_updates_available
```

Known event topics.

# `broadcast`

```elixir
@spec broadcast(
  :buffer_saved | :buffer_opened | :content_replaced,
  Minga.Events.BufferEvent.t()
) :: :ok
@spec broadcast(:buffer_closed, Minga.Events.BufferClosedEvent.t()) :: :ok
@spec broadcast(:buffer_changed, Minga.Events.BufferChangedEvent.t()) :: :ok
@spec broadcast(:mode_changed, Minga.Events.ModeEvent.t()) :: :ok
@spec broadcast(:tool_missing, Minga.Events.ToolMissingEvent.t()) :: :ok
@spec broadcast(:project_rebuilt, Minga.Events.ProjectRebuiltEvent.t()) :: :ok
@spec broadcast(:command_done, Minga.Events.CommandDoneEvent.t()) :: :ok
@spec broadcast(:git_status_changed, Minga.Events.GitStatusEvent.t()) :: :ok
@spec broadcast(:diagnostics_updated, Minga.Events.DiagnosticsUpdatedEvent.t()) :: :ok
@spec broadcast(:lsp_status_changed, Minga.Events.LspStatusEvent.t()) :: :ok
@spec broadcast(:supervisor_restarted, Minga.Events.SupervisorRestartedEvent.t()) ::
  :ok
@spec broadcast(:log_message, Minga.Events.LogMessageEvent.t()) :: :ok
@spec broadcast(:face_overrides_changed, Minga.Events.FaceOverridesChangedEvent.t()) ::
  :ok
@spec broadcast(
  :agent_session_stopped,
  MingaAgent.SessionManager.SessionStoppedEvent.t()
) :: :ok
@spec broadcast(:changeset_merged, MingaAgent.Changeset.MergedEvent.t()) :: :ok
@spec broadcast(
  :changeset_budget_exhausted,
  MingaAgent.Changeset.BudgetExhaustedEvent.t()
) :: :ok
@spec broadcast(:load_user_themes, Minga.Events.LoadUserThemesEvent.t()) :: :ok
@spec broadcast(:buffer_fork_conflict, map()) :: :ok
@spec broadcast(
  :extension_updates_available,
  Minga.Extension.UpdatesAvailableEvent.t()
) :: :ok
```

Broadcasts a typed payload to all subscribers of a topic.

Accepts `BufferEvent` for buffer topics and `ModeEvent` for mode changes.
Using structs with `@enforce_keys` means the compiler catches missing
fields and the type checker flags wrong field types at the call site,
before the event ever reaches subscribers.

Returns `:ok`. If no processes are subscribed to the topic, this is a
no-op with negligible cost.

# `child_spec`

```elixir
@spec child_spec(keyword()) :: Supervisor.child_spec()
```

Returns the child spec for the event bus Registry.

Add this to your supervision tree before any process that subscribes.

# `notify_buffer_changed`

> This function is deprecated. Buffer.Server now broadcasts :buffer_changed automatically on every edit. No manual broadcast needed..

```elixir
@spec notify_buffer_changed(pid()) :: :ok
```

Broadcasts `:buffer_changed` to all subscribers.

Deprecated: use the 2-arity version that accepts a `BufferChangedEvent`
struct with delta and source fields. This 1-arity wrapper exists for
backward compatibility during migration.

# `subscribe`

```elixir
@spec subscribe(topic()) :: :ok
```

Subscribes the calling process to a topic.

The process will be included in `Registry.dispatch/3` callbacks when
`broadcast/2` is called for this topic. A process can subscribe to
multiple topics. Subscribing to the same topic multiple times from
the same process is a no-op.

# `subscribe`

```elixir
@spec subscribe(topic(), term()) :: :ok
```

Subscribes the calling process to a topic with metadata.

The metadata value is passed to the dispatch callback alongside the pid,
which lets subscribers filter or tag their registrations. Subscribing
with the same topic and value from the same process is a no-op.

# `subscribers`

```elixir
@spec subscribers(topic()) :: [pid()]
```

Returns the list of pids subscribed to a topic.

Useful for debugging and testing. Not intended for production dispatch
(use `broadcast/2` instead).

# `unsubscribe`

```elixir
@spec unsubscribe(topic()) :: :ok
```

Unsubscribes the calling process from a topic.

Removes all registrations for this process under the given topic key.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
