MingaEditor.Layout.FooterOverlays (Minga v0.1.0)

Copy Markdown View Source

BEAM visibility source for the seven active footer-band secondary overlay producers (#2281).

These surfaces (float popup, agent context, 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 semantic footer-band z and conservative cell containment. Native GUI frontends still own final rich-content measurement inside those bands. This module is the single place that decides which active producer is visible this frame and how tall its conservative 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, agent_context, and extension_panel): 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 native wrapped content from a bounded semantic line snapshot, so its phantom zone is the remaining ceiling rows above; agent_context renders the live activity spine and may wrap the visible task or todo text; 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.

Live sources (honesty, #2281)

Seven 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, and agent context. The Tool Manager registry identity is preserved elsewhere for compatibility, but it has no footer-overlay producer here because it is not in the render-model UI.

Summary

Types

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.

Types

entry()

@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

visible(state)

@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.