@@ -175,6 +175,33 @@ type (
175175)
176176
177177// UI represents the main user interface model.
178+// renderCache holds the per-frame render memoization the UI consults on
179+// every draw: the sidebar block (drawSidebar re-renders fully each frame),
180+// the resolved large model (an RPC in client mode), and the pills block.
181+// Grouped out of the UI struct so the memoization layer is separable from
182+// model state.
183+type renderCache struct {
184+ // sidebarView is the memoized sidebar block, keyed by sidebarCacheKey
185+ // (a fingerprint of every input affecting its output).
186+ sidebarView string
187+ sidebarCacheKey uint64
188+ hasSidebarCache bool
189+ // sidebarFPBuf is a scratch buffer reused across frames to build the
190+ // sidebar fingerprint without allocating (see sidebarFingerprint).
191+ sidebarFPBuf []byte
192+
193+ // largeModel caches the resolved selected large model — resolving it
194+ // hits the agent coordinator (an RPC in client mode) — keyed by
195+ // largeModelCacheKey. A nil result is never cached so the
196+ // not-ready → ready transition is picked up on the next frame.
197+ largeModel *workspace.AgentModel
198+ largeModelCacheKey string
199+
200+ // pillsView is the memoized pills block, keyed by pillsCacheKey.
201+ pillsView string
202+ pillsCacheKey string
203+}
204+
178205type UI struct {
179206 // ctx is the program's lifecycle context (cancelled on SIGINT / program
180207 // exit). Async workspace operations launched from tea.Cmd closures use
@@ -299,25 +326,9 @@ type UI struct {
299326 // sidebarLogo keeps a cached version of the sidebar sidebarLogo.
300327 sidebarLogo string
301328
302− // sidebar render cache: drawSidebar fully re-renders every frame, so we
303− // memoize the rendered block keyed by a fingerprint of every input that
304− // affects its output.
305− sidebarView string
306− sidebarCacheKey uint64
307− hasSidebarCache bool
308− // sidebarFPBuf is a scratch buffer reused across frames to build the
309− // sidebar fingerprint without allocating (see sidebarFingerprint).
310− sidebarFPBuf []byte
311−
312− // selectedLargeModel cache: resolving the selected large model hits
313− // the agent coordinator, which is an RPC (AgentIsReady + AgentModel)
314− // in client mode. Because the sidebar fingerprint runs every frame we
315− // cache the resolved model keyed by a cheap, locally-cached config
316− // fingerprint and only re-fetch when that changes. A nil result is
317− // never cached so the not-ready → ready transition is picked up on
318− // the next frame.
319− largeModel *workspace.AgentModel
320− largeModelCacheKey string
329+ // renderCache holds the per-frame render memoization (sidebar, pills,
330+ // and resolved large-model caches); see the renderCache type.
331+ renderCache renderCache
321332
322333 // Notification state
323334 notifyBackend notification.Backend
@@ -341,11 +352,6 @@ type UI struct {
341352 pillsAutoExpanded bool
342353 focusedPillSection pillSection
343354 promptQueue int
344− pillsView string
345− // pillsCacheKey fingerprints the inputs used to build pillsView so
346− // renderPills can skip re-rendering (and recomputing the queue
347− // gradient) when nothing that affects the output has changed.
348− pillsCacheKey string
349355
350356 // Todo spinner
351357 todoSpinner spinner.Model
@@ -2552,8 +2558,8 @@ func (m *UI) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
25522558 }
25532559
25542560 m.chat.Draw(scr, layout.main)
2555− if layout.pills.Dy() > 0 && m.pillsView != "" {
2556− uv.NewStyledString(m.pillsView).Draw(scr, layout.pills)
2561+ if layout.pills.Dy() > 0 && m.renderCache.pillsView != "" {
2562+ uv.NewStyledString(m.renderCache.pillsView).Draw(scr, layout.pills)
25572563 }
25582564
25592565 editorWidth := scr.Bounds().Dx()
@@ -4105,9 +4111,9 @@ func (m *UI) newSession() tea.Cmd {
41054111 m.pillsExpanded = false
41064112 m.pillsAutoExpanded = false
41074113 m.promptQueue = 0
4108− m.pillsView = ""
4109− m.pillsCacheKey = ""
4110− m.hasSidebarCache = false
4114+ m.renderCache.pillsView = ""
4115+ m.renderCache.pillsCacheKey = ""
4116+ m.renderCache.hasSidebarCache = false
41114117 m.historyReset()
41124118 agenttools.ResetCache()
41134119 return tea.Batch(