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_heightis 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_heightstays: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 mostband_height - rendered_linesrows tall: float_popup renders1 + 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.
Summary
Functions
Returns the visible footer-band overlays for the frame, back-to-front by z.
Types
@type entry() :: {surface_id :: atom(), content_height :: non_neg_integer() | :max}
A visible footer overlay: its registry surface id and its band content height.
Functions
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.