Skip to content

feat: freeze per-window rendering during minibuffer sessions#8

Merged
chiply merged 2 commits into
mainfrom
feat/freeze-tab-line-in-minibuffer
Jun 13, 2026
Merged

feat: freeze per-window rendering during minibuffer sessions#8
chiply merged 2 commits into
mainfrom
feat/freeze-tab-line-in-minibuffer

Conversation

@chiply

@chiply chiply commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Summary

Completion sessions preview candidate buffers by swapping them into a window (consult, embark, …). Each swap perturbs a per-window bar — most visibly a wrap tab-line, which gains the preview buffer as a tab and can re-flow onto a different number of rows, so the bar pops taller and shorter as the user moves through candidates.

While a minibuffer is active, targets listed in svg-line-freeze-in-minibuffer (default: '(tab-line)) return the display string they last rendered for that window outside a minibuffer, from a window-weak cache (svg-line--freeze-cache). Normal rendering resumes the moment the minibuffer closes. A window with no cached render (created mid-session) falls through to a live render without poisoning the cache.

Redisplay evaluates a window's format with that window selected, so selected-window identifies the cache slot.

Tests

  • freeze-returns-cached-during-minibuffer: cached string returned eq, content fn not re-called; fresh render + cache refresh after
  • freeze-disabled-target-renders-live
  • freeze-uncached-window-renders-fresh: falls through, does not poison the cache
  • All 42 tests pass; compile + linters clean

Verified live

Driving a real consult session with buffer preview: mid-preview the window's tab-line render is eq to the pre-minibuffer string (no re-flow possible), and post-exit rendering is fresh.

Addition: :context-buffer (second commit)

The freeze fixes per-window bars; frame-level bars have the complementary problem: their buffer-dependent segments (buffer name, file icon, modal state, …) read the current buffer, and completion previews flip the selected window's buffer per candidate — so the bar re-rendered with alternating content, repainting as a visible flash (geometry verified constant: 57px throughout reproduction).

:context-buffer takes a function returning the buffer to make current while evaluating :content (nil/dead falls through). A config can pin its tab-bar to the buffer the user came from during minibuffer sessions while time-driven segments (keycast) stay live.

Verified live: 24/24 in-session tab-bar content evaluations ran in the pinned buffer, zero flips (previously alternating Minibuf/original/preview contexts).

Completion sessions preview candidate buffers by swapping them into a
window (consult, embark).  Each swap perturbs a per-window bar -- most
visibly a wrap tab-line, which gains the preview buffer as a tab and
can re-flow onto a different number of rows, popping taller/shorter as
the user moves through candidates.

While a minibuffer is active, targets in svg-line-freeze-in-minibuffer
(default: tab-line) return the display string last rendered for that
window outside a minibuffer, from a window-weak cache; normal rendering
resumes the moment the minibuffer closes.  A window with no cached
render falls through to a live render without poisoning the cache.
Copilot AI review requested due to automatic review settings June 12, 2026 22:22

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a minibuffer-aware “freeze” mode so selected svg-line targets (default tab-line) can reuse their last pre-minibuffer render per window, preventing UI height reflow during completion preview sessions that temporarily swap buffers into windows.

Changes:

  • Introduces svg-line-freeze-in-minibuffer and a weak-key cache (svg-line--freeze-cache) to store the last unfrozen render.
  • Updates svg-line--render to return cached output while a minibuffer is active for configured targets, and to refresh the cache outside minibuffer sessions.
  • Adds ERT tests covering cached returns, disabled freezing, and uncached-window fallback behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
svg-line.el Adds minibuffer-freeze customization + cache and wires freeze behavior into svg-line--render.
test/svg-line-test.el Adds tests validating freeze caching behavior and cache miss handling.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread svg-line.el
Comment on lines +744 to +761
(defcustom svg-line-freeze-in-minibuffer '(tab-line)
"Targets whose windows keep their last render while a minibuffer is active.
Completion sessions preview candidate buffers by swapping them into a
window (consult, embark, ...). Each swap perturbs a per-window bar --
most visibly a `wrap' tab-line, which gains the preview buffer as a tab
and can re-flow onto a different number of rows, so the bar pops taller
and shorter as the user moves through candidates. While a minibuffer is
active, a target listed here keeps showing the display string it last
rendered for that window OUTSIDE the minibuffer; normal rendering
resumes the moment the minibuffer closes. Set to nil to disable."
:type '(repeat (choice (const tab-line) (const header-line)
(const mode-line) (const tab-bar))))

(defvar svg-line--freeze-cache (make-hash-table :test 'eq :weakness 'key)
"WINDOW -> alist of (NAME . DISPLAY-STRING) from the last unfrozen render.
Weak on the window, so entries die with their windows. Read (instead of
rendering) while a minibuffer is active for targets in
`svg-line-freeze-in-minibuffer'; written on every render outside one.")
Comment thread svg-line.el
Comment on lines +1394 to +1413
(let* ((spec (svg-line--spec name))
(freezable (memq (plist-get spec :target)
svg-line-freeze-in-minibuffer))
(win (and freezable (selected-window)))
(in-mini (and freezable (active-minibuffer-window))))
(or (and in-mini win
(cdr (assq name (gethash win svg-line--freeze-cache))))
(let ((str
(if (eq (or (plist-get spec :layout) 'lines) 'wrap)
(let ((s (svg-line-display (car (svg-line--build-wrap spec)))))
(svg-line--store-placements name svg-line--wrap-lh svg-line--wrap-placements)
;; the whole wrap line is tabs, so a hand pointer everywhere fits
(svg-line--interactive s name (and svg-line--wrap-placements t) 'hand))
(let ((svg (svg-line--build-lines spec)))
(svg-line--store-placements name svg-line--lines-lh svg-line--lines-placements)
;; a lines bar has large non-interactive gaps, so no global pointer
(svg-line--interactive (svg-line-display svg) name
(and svg-line--lines-placements t))))))
(when (and win (not in-mini) (window-live-p win))
(setf (alist-get name (gethash win svg-line--freeze-cache)) str))
Completion previews flip the selected window's buffer on every
candidate (original <-> previewed buffer), and a frame-level bar whose
segments read the current buffer (buffer name, file icon, modal state)
re-rendered with different content on each flip -- alternating images
repainting the bar, a visible flash -- while time-driven segments like
keycast should stay live.

:context-buffer takes a function returning the buffer to make current
while evaluating :content (nil or a dead buffer falls through to the
render-time buffer).  Configs can pin a tab-bar to the buffer the user
came from during minibuffer sessions.
@chiply chiply merged commit 182088b into main Jun 13, 2026
7 checks passed
@chiply chiply deleted the feat/freeze-tab-line-in-minibuffer branch June 13, 2026 15:50
@chiply chiply mentioned this pull request Jun 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants