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

BEAM visibility source for the eight footer-band secondary overlays (#2281).

These surfaces (float popup, agent context, tool manager, extension panel,
observatory, edit timeline, notifications, extension overlay) historically had
no BEAM rect: the Go compositor footer-appended one of them, sizing it
frontend-side. The owner ruled them mouse-driven (#2330), so the BEAM now owns
their footer-band geometry. This module is the single place that decides which
of the eight is visible this frame and how tall its band is, reading the SAME
underlying state the render-model builders read (so visibility never drifts
from what the frontend actually renders).

`visible/1` returns the visible footer overlays as `{surface_id, content_height}`
pairs. `MingaEditor.FocusTree` turns each into an overlay node via
`MingaEditor.Layout.OverlayBand.rect/2`; `MingaEditor.Layout.SurfaceRegistry`
then places them with the historical stacking z. Only one overlay shows at a
time in the fixed bottom band (single-active model, #2268 AC-4), and the band
rect is bottom-anchored: its bottom edge sits directly above the minibuffer,
exactly where the Go compositor used to footer-append the overlay.

## Content height and the residual phantom zone

The rect must CONTAIN the rendered overlay (so a click over it is hit-tested and
swallowed) but it should not span the whole band for a short overlay, or the
rows below the content phantom-swallow clicks and the overlay renders well above
the screen bottom. Two buckets:

  * **Count-derivable surfaces** (notifications, observatory, edit_timeline,
    extension_overlay): the BEAM knows the line count the Go renderer draws, so
    `content_height` is that count (clamped by the band ceiling, see the
    per-surface helpers below). The rect is sized to the content and the overlay
    hugs the screen bottom with no residual phantom zone (at most ~1 row of
    conservative slack: e.g. a notification item with no body draws 1 line where
    the count budgets 2).

  * **Wrap-dependent surfaces** (float_popup, extension_panel, and the dormant
    agent_context / tool_manager): these wrap text frontend-side where the BEAM
    only knows an item count, not a wrapped line count, so `content_height` stays
    `:max` (the clamp ceiling). Go bottom-aligns the rendered content within the
    band (`Y = rect.Row + bandHeight - contentLines`), so the overlay still hugs
    the screen bottom; the residual phantom zone (clamp ceiling minus rendered
    lines) sits ABOVE the content, between the buffer and the overlay, not below
    it. Per surface, the phantom band is at most `band_height - rendered_lines`
    rows tall: float_popup renders `1 + min(line_count, ceiling-1)` lines so its
    phantom zone is the remaining ceiling rows above; extension_panel renders a
    title plus up to two blocks per visible panel until it fills the band, so its
    phantom zone shrinks as panels are added; agent_context and tool_manager are
    dormant (never visible today) so their phantom zone is moot until a future
    child gives them a render-model source.

## Live vs dormant sources (honesty, #2281)

Six surfaces have a live BEAM content source in the render-model path and so can
actually become visible here: float popup, extension panel, observatory, edit
timeline, notifications, extension overlay. Two are dormant on that path: the
agent-context builder is a permanent `visible: false` stub
(`MingaEditor.RenderModel.UI.AgentContextBuilder`, fed by `gui_payload/1` which
the traditional shell always returns `nil` for), and the tool manager is not in
the render-model UI at all (it ships only via the legacy `protocol/gui.ex`
path). Their predicates here are wired to the same future sources, so they are
placement-ready and will light up automatically when an epic child gives them a
BEAM render-model source. Today their predicates never fire, which is correct:
an overlay the BEAM never renders must not claim a footer band.

# `entry`

```elixir
@type entry() :: {surface_id :: atom(), content_height :: non_neg_integer() | :max}
```

A visible footer overlay: its registry surface id and its band content height.

# `visible`

```elixir
@spec visible(map()) :: [entry()]
```

Returns the visible footer-band overlays for the frame, back-to-front by z.

Each entry is `{surface_id, content_height}` where `surface_id` is a
`MingaEditor.Layout.SurfaceRegistry` surface id and `content_height` is the
BEAM-known content line count for the count-derivable surfaces (clamped by the
band ceiling) or `:max` for the wrap-dependent ones (see the moduledoc).

Order is ascending z (back-to-front): extension overlay (lowest z, painted
first) up to float popup (highest z, painted last). This ordering matters for
hit-testing: `FocusTree.add_footer_band_overlays/3` appends each entry as a
child in list order, and `FocusTree`'s hit path reverses children so the
LAST-appended (highest z) node wins among same-rect siblings. Returning
ascending z here makes the BEAM hit-winner the highest-z node, the SAME surface
Go composites on top (its highest-z render winner). If this returned descending
z, the lowest-z node would win the reversed hit-test, inverting it against the
render winner and breaking the per-surface handlers #2330 builds on top.

---

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