# `MingaEditor.Renderer.RenderWindow`
[🔗](https://github.com/jsmestad/minga/blob/main/lib/minga_editor/renderer/render_window.ex#L1)

A window is a viewport into a buffer.

Each window holds a reference to a buffer process and its own independent
viewport (scroll position and dimensions). Multiple windows can reference
the same buffer; edits in one are visible in all.

## Render cache and dirty-line tracking

Windows carry per-frame render state that enables incremental rendering.
The semantic `RenderModel.Window.Builder` reuses retained composed rows for
lines whose inputs are unchanged and only recomposes lines marked as dirty.

The dirty set uses two representations:
- `:all` means every line needs re-rendering (used for scroll, resize,
  theme change, highlight update, and other wholesale invalidation)
- A map of specific buffer line numbers (`%{line => true}`) that need re-rendering
  (used for edits that touch a few lines)

Tracking fields (`last_viewport_top`, `last_viewport_cache_key`,
`last_gutter_w`, `last_line_count`, `last_cursor_line`, `last_buf_version`)
store the values from the previous frame. The Scroll stage compares current
values against these to detect full-invalidation triggers automatically.

# `id`

```elixir
@type id() :: pos_integer()
```

Unique identifier for a window.

# `t`

```elixir
@type t() :: %MingaEditor.Renderer.RenderWindow{
  authoritative_scroll_seq: non_neg_integer(),
  content: MingaEditor.Window.Content.t(),
  cursor: Minga.Buffer.position(),
  document_symbols: [Minga.Language.Symbol.t()],
  fold_map: MingaEditor.FoldMap.t(),
  fold_ranges: [Minga.Editing.Fold.Range.t()],
  id: id(),
  pinned: boolean(),
  popup_meta: MingaEditor.UI.Popup.Active.t() | nil,
  render_cache: MingaEditor.Renderer.WindowCache.t(),
  scroll_detach_cursor: Minga.Buffer.position() | nil,
  scroll_echo_top: integer() | nil,
  scroll_velocity: MingaEditor.Window.ScrollVelocity.t(),
  textobject_positions: %{
    required(atom()) =&gt; [{non_neg_integer(), non_neg_integer()}]
  },
  viewport: MingaEditor.Viewport.t()
}
```

# `applied_change_sequence`

```elixir
@spec applied_change_sequence(t()) :: non_neg_integer()
```

Returns the applied buffer change sequence for durable line identity.

# `authoritative_scroll_seq`

```elixir
@spec authoritative_scroll_seq(t()) :: non_neg_integer()
```

Returns the editor-owned authoritative-scroll request counter (#2652).

# `cached_total_visual_rows`

```elixir
@spec cached_total_visual_rows(t(), term()) :: non_neg_integer() | nil
```

Returns a cached wrapped visual row total when the key matches.

# `changed_snapshot`

```elixir
@spec changed_snapshot(t()) :: Minga.Buffer.RenderSnapshot.t() | nil
```

Returns the atomic bounded snapshot for pending resident deltas.

# `content_epoch`

```elixir
@spec content_epoch(t()) :: non_neg_integer()
```

Returns the window's durable content epoch.

# `detect_context_change`

```elixir
@spec detect_context_change(
  t(),
  MingaEditor.Renderer.WindowCache.context_fingerprint()
) :: t()
```

Compares the current render context fingerprint against the last frame's.

If the fingerprint changed, marks all lines dirty. This catches changes
to visual selection, search matches, syntax highlights, diagnostic signs,
git signs, horizontal scroll, active/inactive status, and theme colors,
all of which affect every visible line's draw output.

# `detect_invalidation`

```elixir
@spec detect_invalidation(
  t(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer()
) :: t()
```

Checks current frame parameters against last-frame tracking fields
and returns the window with `dirty_lines: :all` if anything that
requires a full redraw has changed.

Structural triggers (checked here): viewport scroll, gutter width,
line count, buffer version, first frame (sentinel values).

Context triggers (checked separately via `detect_context_change/2`):
visual selection, search matches, syntax highlights, diagnostic signs,
git signs, viewport horizontal scroll, active status, theme colors.

# `detect_invalidation`

```elixir
@spec detect_invalidation(
  t(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer()
) :: t()
```

# `detect_invalidation`

```elixir
@spec detect_invalidation(
  t(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer()
) :: t()
```

# `dirty?`

```elixir
@spec dirty?(t(), non_neg_integer()) :: boolean()
```

Returns true if the given buffer line needs re-rendering.

Always true when `dirty_lines` is `:all`.

# `expected_buffer_version`

```elixir
@spec expected_buffer_version(t()) :: non_neg_integer() | nil
```

Returns the renderer-consumed version pinned for bounded line fetches.

# `fold_all`

```elixir
@spec fold_all(t()) :: t()
```

Folds all available ranges.

# `fold_at`

```elixir
@spec fold_at(t(), non_neg_integer()) :: t()
```

Folds the range containing the given buffer line.

# `fold_recursive_at`

```elixir
@spec fold_recursive_at(t(), non_neg_integer()) :: t()
```

Folds the outermost range containing the given buffer line and every nested range.

# `has_folds?`

```elixir
@spec has_folds?(t()) :: boolean()
```

Returns true if this window has any active folds.

# `hydration_reason`

```elixir
@spec hydration_reason(t()) :: atom() | nil
```

Returns the explicit reason for the next resident hydration.

# `invalidate`

```elixir
@spec invalidate(t()) :: t()
```

Marks all lines dirty (full redraw needed).

Clears all caches and resets tracking fields to sentinels so the next
render pass starts from scratch. Use this when the window's buffer
changes, on resize, or any other event that makes all cached draws
invalid.

# `line_identity`

```elixir
@spec line_identity(t()) :: Minga.RenderModel.Window.LineIdentity.t() | nil
```

Returns the window's durable logical-line identity sequence.

# `mark_authoritative_scroll`

```elixir
@spec mark_authoritative_scroll(t()) :: t()
```

Records that an authoritative BEAM-initiated viewport jump must discard any
frontend-held local scroll offset, even if the committed top is unchanged (#2652).

Command handlers call this on the live window (editor process): at dispatch
for the always-authoritative viewport commands, and from the success branches
of failable jumps (search hits, mark jumps, bracket match, LSP goto). The
`MingaEditor.Commands` `@authoritative_scroll_commands` comment documents the
policy and is the source of truth for the command set.
It is an editor-owned, top-level `Window` field (never in the render cache), a
monotonic request counter incremented once per authoritative jump. Like
`scroll_echo_top`, only the editor writes it, on the live window, so the async
render writeback (which copies back only the render cache) cannot clobber it.

`settle_scroll_seq/1` consumes it: the render cache remembers the last request
count it settled against and advances `scroll_seq` whenever this counter moved
past that baseline. Because the baseline (in the render cache) is overwritten to
the observed counter every settle rather than the counter being cleared here, a
single increment produces exactly one bump per rendered lineage and cannot latch
(see `MingaEditor.Window.RenderCache.settle_scroll_seq/4`). This closes the
same-top gap that the settle-time top comparison alone cannot see (a `zz` while
already centered, a search hit already on screen).

# `mark_dirty`

```elixir
@spec mark_dirty(t(), [non_neg_integer()] | :all) :: t()
```

Marks specific buffer lines as needing re-render.

Pass `:all` to force a complete redraw (scroll, resize, theme change, etc.).
Pass a list of buffer line numbers for targeted invalidation (edits).
If the window is already fully dirty, adding specific lines is a no-op.

# `mark_frontend_reset_pending`

```elixir
@spec mark_frontend_reset_pending(t()) :: t()
```

Marks the next retained GUI frame as a frontend-state reset without discarding TUI draw caches.

# `mark_scroll_echo`

```elixir
@spec mark_scroll_echo(t(), integer()) :: t()
```

Records the committed viewport top of a frontend-reported free-scroll (#2661).

Set by the mouse-wheel/trackpad input path to the top it just committed. It is
an editor-owned, top-level `Window` field (never in the render cache) so the
async render writeback cannot clobber a newer value: only the input path ever
writes it, on the live window. It is deliberately sticky — never cleared. A
later wheel overwrites it; `settle_scroll_seq/1` treats a viewport top equal to
this value as an echo of the frontend's own report, so it does not advance
`scroll_seq` (the "echo-loop guard": no re-anchor storm during a wheel/trackpad scroll gesture).

# `new`

```elixir
@spec new(id(), pid(), pos_integer(), pos_integer()) :: t()
```

Creates a new window with the given id, buffer, and viewport dimensions.

# `new`

```elixir
@spec new(id(), pid(), pos_integer(), pos_integer(), Minga.Buffer.position()) :: t()
```

Creates a new window with the given id, buffer, viewport dimensions, and cursor position.

# `new_agent_chat`

```elixir
@spec new_agent_chat(id(), pos_integer(), pos_integer()) :: t()
```

Creates a new semantic agent chat window.

# `new_empty_state`

```elixir
@spec new_empty_state(id(), pos_integer(), pos_integer()) :: t()
```

Creates a new window showing the zero-buffers launchpad surface (#2689).

# `next_textobject`

```elixir
@spec next_textobject(t(), atom(), {non_neg_integer(), non_neg_integer()}) ::
  {non_neg_integer(), non_neg_integer()} | nil
```

Finds the next textobject position of the given type after (row, col).

# `pending_edit_deltas`

```elixir
@spec pending_edit_deltas(t()) :: [Minga.Buffer.EditDelta.t()]
```

Returns renderer-consumed edit deltas pending resident composition.

# `popup?`

```elixir
@spec popup?(t()) :: boolean()
```

Returns true if this window is a popup (has popup metadata attached).

# `prepare_render_epoch`

```elixir
@spec prepare_render_epoch(t(), term()) :: {t(), non_neg_integer(), boolean()}
```

Prepares the retained GUI content epoch for the current frame.

# `prev_textobject`

```elixir
@spec prev_textobject(t(), atom(), {non_neg_integer(), non_neg_integer()}) ::
  {non_neg_integer(), non_neg_integer()} | nil
```

Finds the previous textobject position of the given type before (row, col).

# `put_lineage`

```elixir
@spec put_lineage(
  t(),
  Minga.RenderModel.Window.LineIdentity.t(),
  non_neg_integer()
) :: t()
```

Overlays renderer-owned committed lineage onto a window snapshot.

# `put_resident_build`

```elixir
@spec put_resident_build(t(), MingaEditor.RenderModel.Window.ResidentBuild.t() | nil) ::
  t()
```

Stores the current frame's residence build state for incremental reuse next frame (#2658).

# `put_retained_rows`

```elixir
@spec put_retained_rows(t(), %{
  optional(non_neg_integer()) =&gt; MingaEditor.Renderer.WindowCache.retained_row()
}) :: t()
```

Stores the current frame's retained composed rows for upstream reuse next frame (#2287).

# `put_retained_wrap_lines`

```elixir
@spec put_retained_wrap_lines(
  t(),
  %{
    optional(non_neg_integer()) =&gt;
      MingaEditor.Renderer.WindowCache.retained_wrap_line()
  }
) :: t()
```

Stores the current frame's retained wrapped logical lines for upstream reuse next frame (#2287).

# `put_row_slot_allocator`

```elixir
@spec put_row_slot_allocator(t(), Minga.RenderModel.Window.RowSlotAllocator.t()) ::
  t()
```

Stores the producer-owned stable row-slot allocator.

# `put_total_visual_rows`

```elixir
@spec put_total_visual_rows(t(), term(), non_neg_integer()) :: t()
```

Stores the wrapped visual row total for the current cache key.

# `record_scroll_event`

```elixir
@spec record_scroll_event(t(), integer(), Minga.Buffer.position()) :: t()
```

Records a wheel/trackpad scroll event so the render pipeline can tell a scroll
gesture is in progress.

Advances the scroll-rate estimator (`scroll_follow_cursor?/3` reads its tier to
suppress cursor re-anchoring mid-gesture) and marks `scroll_detach_cursor` at
the cursor position at gesture start, so the viewport stays put until the cursor
actually moves.

# `reset_content_identity`

```elixir
@spec reset_content_identity(t(), Minga.Buffer.RenderSnapshot.t()) :: t()
```

Explicitly rebuilds durable content identity in a fresh epoch.

# `residence_armed?`

```elixir
@spec residence_armed?(t()) :: boolean()
```

Returns whether residence was armed by the previous eligible frame (#2679).

# `resident?`

```elixir
@spec resident?(t()) :: boolean()
```

Returns whether this window was a full-document resident window as of the last rendered frame.

# `resident_build`

```elixir
@spec resident_build(t()) :: MingaEditor.RenderModel.Window.ResidentBuild.t() | nil
```

Returns the persistent full-document residence build state (#2658).

# `resize`

```elixir
@spec resize(t(), non_neg_integer(), non_neg_integer()) :: t()
```

Updates the viewport dimensions for this window, marking all lines dirty.

# `retained_rows`

```elixir
@spec retained_rows(t()) :: %{
  optional(non_neg_integer()) =&gt; MingaEditor.Renderer.WindowCache.retained_row()
}
```

Returns the retained composed rows from the previous semantic content build (#2287).

# `retained_wrap_lines`

```elixir
@spec retained_wrap_lines(t()) :: %{
  optional(non_neg_integer()) =&gt;
    MingaEditor.Renderer.WindowCache.retained_wrap_line()
}
```

Returns the retained wrapped logical lines from the previous semantic content build (#2287).

# `row_slot_allocator`

```elixir
@spec row_slot_allocator(t()) :: Minga.RenderModel.Window.RowSlotAllocator.t()
```

Returns the producer-owned stable row-slot allocator.

# `scroll_follow_cursor?`

```elixir
@spec scroll_follow_cursor?(t(), Minga.Buffer.position(), integer()) ::
  {t(), boolean()}
```

# `scroll_horizontal`

```elixir
@spec scroll_horizontal(t(), integer()) :: t()
```

Scrolls the window horizontally by display columns.

# `scroll_seq`

```elixir
@spec scroll_seq(t()) :: non_neg_integer()
```

Returns the renderer-owned monotonic scroll-authority sequence.

# `scroll_velocity_tier`

```elixir
@spec scroll_velocity_tier(t(), integer()) :: MingaEditor.Window.ScrollVelocity.tier()
```

# `scroll_viewport`

```elixir
@spec scroll_viewport(t(), integer(), non_neg_integer()) :: t()
```

Scrolls the window's viewport by `delta` lines and updates pinned state.

Scrolling up always unpins. Scrolling down re-pins only when the viewport
reaches the bottom. `total_lines` is the buffer's line count.

Returns the updated window.

# `set_document_symbols`

```elixir
@spec set_document_symbols(t(), [Minga.Language.Symbol.t()]) :: t()
```

Updates the document symbols available for this window.

# `set_fold_ranges`

```elixir
@spec set_fold_ranges(t(), [Minga.Editing.Fold.Range.t()]) :: t()
```

Updates the available fold ranges (from a provider). Preserves existing folds that still exist in the new ranges.

# `set_pinned`

```elixir
@spec set_pinned(t(), boolean()) :: t()
```

Sets whether the window should stay pinned to the bottom while content streams.

# `set_residence_armed`

```elixir
@spec set_residence_armed(t(), boolean()) :: t()
```

Arms or disarms residence promotion for the next frame (#2679 first-paint-then-promote).

# `set_resident`

```elixir
@spec set_resident(t(), boolean()) :: t()
```

Records whether this window is a full-document resident window (#2653/#2658).

Stored in the render cache because residence is a renderer-computed value and
the render cache is the only per-window struct copied back from the async
render pipeline (see `MingaEditor.State.merge_renderer_window/2`). The input
layer (mouse wheel/trackpad handling) reads it via `resident?/1` so it can
branch on residence without recomputing it. Stale by at most one frame, which
is harmless: residence is a document-size property that doesn't flip mid-gesture.

# `set_viewport`

```elixir
@spec set_viewport(t(), MingaEditor.Viewport.t()) :: t()
```

Stores the computed viewport for this window.

# `settle_scroll_seq`

```elixir
@spec settle_scroll_seq(t()) :: t()
```

Settles the per-frame `scroll_seq` decision against the render cache baseline.

Delegates to `MingaEditor.Window.RenderCache.settle_scroll_seq/4` with this
frame's committed viewport top, the sticky `scroll_echo_top` recorded by the
input path, and the `authoritative_scroll_seq` request counter set by command
handlers. `scroll_seq` advances when EITHER an authoritative jump was marked
since the last settle OR the top moved to a value that is neither the previous
committed top nor a frontend-reported free-scroll top (a genuine BEAM-initiated
anchor move). Wheel/trackpad free-scroll frames share the reported top, so they are
echoes and do not advance the sequence. A jump that also moves the top bumps
once, not twice (a single OR decision per settle). The counter, its baseline,
and the authoritative-request baseline all live in the render cache, so the
sequence is monotonic across the serially threaded, written-back render cache.

# `show_buffer`

```elixir
@spec show_buffer(t(), pid()) :: t()
```

Switches the window from the launchpad back to a buffer.

# `show_empty_state`

```elixir
@spec show_empty_state(t()) :: t()
```

Switches the window to the zero-buffers launchpad surface (#2689).

The window stays open (the window tree never drops its last leaf); only
its content changes, mirroring how agent chat panes host non-buffer
content. Any cached buffer rendering is invalidated.

# `snapshot_after_render`

```elixir
@spec snapshot_after_render(
  t(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  MingaEditor.Renderer.WindowCache.context_fingerprint()
) :: t()
```

Snapshots tracking fields after a successful render pass.

Clears the dirty set and records the current frame's parameters so the
next frame can detect what changed. The context fingerprint captures
all per-frame render context inputs (visual selection, search matches,
syntax highlights, signs, etc.) so context changes trigger full redraws.

# `snapshot_after_render`

```elixir
@spec snapshot_after_render(
  t(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  MingaEditor.Renderer.WindowCache.context_fingerprint()
) :: t()
```

# `sync_line_identity`

```elixir
@spec sync_line_identity(t(), Minga.Buffer.RenderSnapshot.t()) :: t()
```

Reconciles durable logical-line identities from an atomic buffer snapshot.

# `toggle_fold`

```elixir
@spec toggle_fold(t(), non_neg_integer()) :: t()
```

Toggles the fold at the given buffer line using the window's available fold ranges.

# `unfold_all`

```elixir
@spec unfold_all(t()) :: t()
```

Unfolds all folds.

# `unfold_at`

```elixir
@spec unfold_at(t(), non_neg_integer()) :: t()
```

Unfolds the range containing the given buffer line.

# `unfold_containing`

```elixir
@spec unfold_containing(t(), [non_neg_integer()]) :: t()
```

Unfolds any folds that contain the given lines (used by search auto-unfold).

# `unfold_recursive_at`

```elixir
@spec unfold_recursive_at(t(), non_neg_integer()) :: t()
```

Unfolds every active fold inside the outermost range containing the given buffer line.

---

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