Vectrum
Sign In

tui: the welcome block follows the model it names

Switching models left the header naming the one the session opened on. It is
the header, not a record of how things started, so it is re-rendered on a
switch the same way it already is on a resize — one helper for both, rather
than two places that know how that entry is built.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Jjonicommitted 1 parent 3f44aafcommit52abb14

Showing 3 changed files with 37 additions and 6 deletions.

+17−6internal/tui/model.goView File
@@ -305,17 +305,28 @@ func (m *model) resize(w, h int) tea.Cmd {
305305 } else {
306306 m.vp.Width = w
307307 }
308- // The welcome block is laid out for a width, so it is re-rendered rather
309- // than wrapped: it is the one entry that owns its own lines.
310- if len(m.tr.entries) > 0 && m.tr.entries[0].kind == entryRaw {
311- m.tr.entries[0].text = splash(m.opts, w)
312- m.tr.dirty = true
313- }
308+ m.refreshSplash()
314309 m.vp.SetContent(m.tr.render())
315310 m.vp.GotoBottom()
316311 return nil
317312 }
318313
314+ // refreshSplash re-renders the welcome block. It is the one entry that owns
315+ // its own lines — laid out for a width, and naming the model and the endpoint
316+ // — so when any of those change it is rebuilt rather than wrapped or left to
317+ // describe a session that has moved on.
318+ func (m *model) refreshSplash() {
319+ if len(m.tr.entries) == 0 || m.tr.entries[0].kind != entryRaw {
320+ return
321+ }
322+ width := m.width
323+ if width <= 0 {
324+ width = 80
325+ }
326+ m.tr.entries[0].text = splash(m.opts, width)
327+ m.tr.dirty = true
328+ }
329+
319330 // sync re-renders the scrollback, keeping the view pinned to the bottom unless
320331 // the user has scrolled up to read something.
321332 func (m *model) sync() tea.Cmd {
+1−0internal/tui/model_picker.goView File
@@ -122,6 +122,7 @@ func (m *model) onModelKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
122122 // which is the point of switching mid-session.
123123 func (m *model) applyModel(id string) {
124124 m.opts.Client.SetModel(id)
125+ m.refreshSplash()
125126 m.tr.addNotice("model: " + prettyModel(id) + " (" + id + ") — from the next turn on")
126127 }
127128
+19−0internal/tui/model_picker_test.goView File
@@ -323,3 +323,22 @@ func TestModelRowsShowTheContextWindow(t *testing.T) {
323323 }
324324 }
325325 }
326+
327+ // The welcome block names the model, so it has to follow a switch: it is the
328+ // header of the session, not a record of how the session started.
329+ func TestWelcomeBlockFollowsTheModel(t *testing.T) {
330+ m := newTestModel(t)
331+ if !strings.Contains(m.tr.entries[0].text, "Opus 5") {
332+ t.Fatalf("the welcome block does not name the model it started on:\n%s", m.tr.entries[0].text)
333+ }
334+
335+ m.submit("/model claude-haiku-4-5")
336+
337+ welcome := m.tr.entries[0].text
338+ if !strings.Contains(welcome, "Haiku 4.5") {
339+ t.Errorf("the welcome block still does not name the new model:\n%s", welcome)
340+ }
341+ if strings.Contains(welcome, "Opus 5") {
342+ t.Errorf("the welcome block names both models:\n%s", welcome)
343+ }
344+ }