# `MingaEditor.Layout.SurfaceRegistry`
[🔗](https://github.com/jsmestad/minga/blob/main/lib/minga_editor/layout/surface_registry.ex#L1)

Pure surface registry: the single source for "what is where on screen".

Given the frame's editor state, `placements/1` returns an ordered list of
`MingaEditor.Layout.SurfaceRegistry.Placement` entries, each carrying a
`surface_id`, a `rect` (terminal cells, the existing `Layout.rect/0`
convention), a `z` band, and a `hit_kind`. The list is ordered back-to-front
by `z` (lowest first), so a stable sort by `z` reproduces paint order and a
reverse walk reproduces hit-test precedence.

This module is a calculation, not a process: state in, placements out. There
is no ETS, no GenServer, no cache of its own. It is consumed where
`MingaEditor.FocusTree`/`MingaEditor.Layout` are consumed today.

## The input rule (design of record, epic #2330)

Clients resolve clicks on content they render and send semantic intents
(`gui_actions`); the BEAM owns placement, stacking, and containment for
registry-placed surfaces. That is the governing line for all surface input.

A frontend hit-tests its own rendered content (a completion row, a
notification action, an observatory node) and emits an intent like
"item N clicked", exactly as SwiftUI's native hit-test already does. The BEAM
does not re-derive what a click means on rendered content. What it owns is
structure: which rect a surface occupies (placements), which surface wins when
rects overlap (z-order arbitration, since stacking depends on editor state only
the BEAM has), and containment, so a click that misses every interactive
element of a registry-placed surface is swallowed instead of falling through to
the buffer underneath (`MingaEditor.Input.OverlaySink`). The picker is the one
documented exception: it predates this rule and stays BEAM-resolved as shipped.

## One source, derived from the focus tree

The registry is built by flattening the existing `MingaEditor.FocusTree`. The
focus tree is already the BEAM's authority for mouse routing: it carries the
per-frame `Layout` rects plus the single active overlay, with children stored
in rendered z-order (back to front). By projecting that same tree into
placement entries, the registry rect for every surface is, by construction,
the exact rect the focus tree (and therefore every hit-test that routes
through it) uses. That is the behaviour-neutrality guarantee the epic asks
for: registry rect == the rect each handler previously computed, because both
read the same tree.

## Scope honesty: single active overlay

`FocusTree.add_modal_overlays/3` encodes the same exclusive precedence that
Go's `overlayLines()` chain encodes today: at most one modal overlay (picker
OR completion) is live per frame. The registry preserves that decision as
data; it emits the single active overlay and nothing else. Multiple
simultaneous overlays are newly *expressible* as a list but are deliberately
NOT produced here. Enabling them is out of scope (see #2268, AC-4).

Enumeration history (#2268 -> #2281). The Go compositor's `overlayLines()` chain
once stacked surfaces that were not focus-tree nodes via a hand-ordered rank
table. That table is now gone: every overlay surface is a focus-tree node with a
BEAM-authoritative rect.

* **Cursor-anchored popups: hover popup, signature help.** Both are floating
  popups whose exact on-screen box the BEAM computes for layout
  (`HoverPopup.box/3`/`SignatureHelp.box/3`, driven by `FloatingWindow`).
  `FocusTree.add_floating_overlays/2` adds them as overlay nodes from
  `shell_state`; they occupy the `@z_floating_overlay` region (hover 290 >
  signature help 280).

* **Footer-band secondary overlays (#2281): float popup, agent context, tool
  manager, extension panel, observatory, edit timeline, notifications, extension
  overlay.** The owner ruled these mouse-driven (#2330), so the BEAM owning their
  footer-band geometry is now the *designed* layout. `FocusTree.add_footer_band_overlays/3`
  adds each visible one (per `MingaEditor.Layout.FooterOverlays`) as an overlay
  node with a bottom-anchored full-width rect from `MingaEditor.Layout.OverlayBand`
  (porting the Go `maxOverlayHeight` clamp). They carry the exact historical
  stacking z (270/260/240/190/180/170/160/150), so Go composites the single
  highest-z winner by its placement rect instead of footer-appending. The
  single-active model still holds (#2268 AC-4): the tree may express several
  placements, but Go renders one. Their click events route to
  `MingaEditor.Input.OverlaySink`, which swallows mouse events so a click over a
  visible overlay never reaches the buffer underneath (AC-2). Per-surface
  activation semantics land as epic #2330 children.

## surface_id namespace and the identity-unification call (#2268)

`surface_id/1` maps each focus-tree `content_type` to a stable atom in the
registry's namespace; `surface_id_u16/1` maps that atom to its `u16` wire
value and `hit_kind_u8/1` maps a `hit_kind` atom to its `u8` wire value. This
module is the single source of both mappings.

**Unification decision (#2268 proper):** the schema (`surface_placement` in
`docs/protocol_schema.toml`, generated decoders on every frontend) carries
`surface_id` as a raw `u16` and `hit_kind` as a raw `u8`. It deliberately does
NOT add a `surface_id`/`hit_kind` enum: that would be a new schema vocabulary,
and the consult's instruction was to keep the schema as the cross-language
source of truth *without* a new vocabulary. The cross-language source of truth
is therefore the wire shape plus the generated codec; the numeric identity of
each surface stays authoritative here, and the emitter
(`Minga.Frontend.Adapter.GUI.SurfaceLayoutEncoder`) *consumes* these functions
rather than re-deriving numbers. One writer (this module), one reader (the
encoder). `hit_kind_u8/1` reuses the window-encoder hit-kind numbering
(`Minga.Frontend.Adapter.GUI.WindowEncoder` 1..6) and extends it with
`:chrome` (7) and `:overlay` (8) so a placement's hit kind and a window hit
region speak the same u8.

## hit_kind

`hit_kind` reuses the window-scoped convention already encoded by
`Minga.RenderModel.Window.HitRegion` and `Minga.Frontend.Adapter.GUI.WindowEncoder`
(`:text`, `:gutter`, `:fold_control`, `:modeline`, `:status_bar`, `:divider`).
Surface-level entries extend it with `:chrome` (structural chrome that routes
to a handler but is not buffer text) and `:overlay` (a modal overlay surface).
It is a coarse classification of what a click on the surface means, not a
precise intra-surface region; intra-window hit regions stay with the window
encoder.

## What is NOT unified here (documented per the epic)

Several handlers compute a region's *interpretation* from math that is too
entangled to swap behind a rect lookup without rewriting interaction
semantics. For those, the registry is the source of the surface RECT, but the
handler keeps its own interpretation:

* `MingaEditor.Input.AgentMouse` splits the agent window content rect into a
  chat sub-column vs a preview sub-column using `chat_width_pct` math, and
  splits the prompt area off the bottom using `PromptRenderer` height math.
  The registry emits the agent window/panel rect; the chat/preview/prompt
  sub-division stays in `AgentMouse` (it is interaction semantics, not a
  placed surface). Forcing it into the registry would mean inventing
  sub-surfaces that nothing else places.
* Tab-bar and modeline *segment* click regions
  (`tab_bar_click_regions`, `modeline_click_regions`) are authored at render
  time as text-property spans, not rects. The registry places the tab_bar and
  status_bar/modeline surfaces; the per-segment command lookup stays where it
  is.
* Intra-window buffer geometry (gutter width, fold column, scroll position to
  buffer line) stays in `MingaEditor.Mouse.HitTest`. The registry places the
  window content rect; translating a cell to a buffer position is window
  interpretation, not surface placement.

These are left intentionally. The registry's job in this slice is to be the
one authority for surface *rects and z-order*, not to absorb every handler's
interpretation of a click inside its surface.

# `hit_kind`

```elixir
@type hit_kind() ::
  :text
  | :gutter
  | :fold_control
  | :modeline
  | :status_bar
  | :divider
  | :chrome
  | :overlay
```

Coarse classification of what a click on a surface means.

# `surface_id`

```elixir
@type surface_id() ::
  :tab_bar
  | :editor_area
  | :window
  | :buffer_content
  | :agent_chat_window
  | :agent_chat_content
  | :modeline
  | :file_tree
  | :sidebar
  | :custom_sidebar
  | :agent_panel
  | :status_bar
  | :minibuffer
  | :bottom_panel
  | :picker_backdrop
  | :picker
  | :completion_backdrop
  | :completion_menu
  | :hover_popup
  | :signature_help
  | :float_popup
  | :agent_context
  | :tool_manager
  | :extension_panel
  | :observatory
  | :edit_timeline
  | :notifications
  | :extension_overlay
```

A surface identity in the registry's namespace.

# `wire_placement`

```elixir
@type wire_placement() :: %{
  surface_id: 0..65535,
  rect: %{
    row: non_neg_integer(),
    col: non_neg_integer(),
    width: non_neg_integer(),
    height: non_neg_integer()
  },
  z: non_neg_integer(),
  hit_kind: 0..255
}
```

A placement projected to its wire shape: surface_id/hit_kind already mapped to
their numeric identity, rect as a `{row, col, width, height}` cell map, z verbatim.

# `contains?`

```elixir
@spec contains?(MingaEditor.Layout.rect(), integer(), integer()) :: boolean()
```

Half-open rect containment: `row in [r, r+h)` and `col in [c, c+w)`.

# `from_tree`

```elixir
@spec from_tree(MingaEditor.FocusTree.t()) :: [
  MingaEditor.Layout.SurfaceRegistry.Placement.t()
]
```

Builds placements from an already-constructed focus tree.

Exposed so callers that already hold the cached tree (mouse routing, render
input) do not rebuild it. The tree's child order is rendered z-order; this
walk assigns each node a `z` band and preserves back-to-front ordering.

# `hit_kind_u8`

```elixir
@spec hit_kind_u8(hit_kind()) :: 0..255
```

Maps a registry `hit_kind` atom to its `u8` wire value.

Reuses the window-encoder hit-kind numbering
(`Minga.Frontend.Adapter.GUI.WindowEncoder`: text 1, gutter 2, fold_control 3,
modeline 4, divider 5, status_bar 6) so a placement's hit kind and a per-window
hit region speak the same u8, and extends it with `:chrome` (7) and `:overlay`
(8) for surface-level entries. Consumed by `SurfaceLayoutEncoder`.

# `placements`

```elixir
@spec placements(map()) :: [MingaEditor.Layout.SurfaceRegistry.Placement.t()]
```

Returns the frame's surface placements ordered back-to-front by `z`.

Pure: takes editor or render-pipeline state and returns a list of
`Placement` structs. The list is the single source of surface rects and
z-order for both compositing (sort by `z`) and hit-testing (reverse the
sorted list).

# `rect_for`

```elixir
@spec rect_for(map(), surface_id()) :: MingaEditor.Layout.rect() | nil
```

Returns the rect of the first placed surface with `surface_id`, or `nil`.

This is the read site hit-testers use to ask the registry "where is surface
X?" instead of re-deriving the rect from `Layout` fields. Because placements
are projected from the focus tree, this rect is the same one mouse routing
hit-tests against. When several surfaces share an id (e.g. `:modeline` per
window), the frontmost (highest `z`, last in paint order) is returned.

# `rect_for_in`

```elixir
@spec rect_for_in([MingaEditor.Layout.SurfaceRegistry.Placement.t()], surface_id()) ::
  MingaEditor.Layout.rect() | nil
```

Like `rect_for/2` but reads an already-computed placement list.

# `surface_id`

```elixir
@spec surface_id(MingaEditor.FocusTree.Node.content_type()) :: surface_id() | nil
```

Maps a focus-tree `content_type` to a registry `surface_id`, or `nil` for
content types that are not independently placed surfaces (the viewport root
and the per-window `:window` container, whose content child is the placed
surface).

# `surface_id_u16`

```elixir
@spec surface_id_u16(surface_id()) :: 0..65535
```

Maps a registry `surface_id` atom to its `u16` wire value.

Single BEAM-side source of the surface numbering. The schema carries
`surface_id` as a raw `u16` (no enum, by decision: see the moduledoc), and
`Minga.Frontend.Adapter.GUI.SurfaceLayoutEncoder` consumes this function
rather than re-deriving numbers.

# `wire_placements`

```elixir
@spec wire_placements(map()) :: [wire_placement()]
```

Returns the frame's placements projected to their wire shape.

This is the boundary between the registry (which owns the surface/hit-kind
numbering) and the wire encoder (which only lays out bytes). The encoder
consumes these plain maps, so it never depends on `MingaEditor.*`: the registry
stays the single authority for the numeric identity (#2268 unification call),
and the emitter stays a pure byte layout over data. Order is preserved
(back-to-front by z), so the wire list IS the compositing order.

# `within?`

```elixir
@spec within?(map(), surface_id(), integer(), integer()) :: boolean()
```

Returns true when `(row, col)` falls inside the placed surface `surface_id`.

Half-open rect containment matching `FocusTree.Node.contains?/3`, so a
registry-backed bounds check agrees with focus-tree hit-testing.

---

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