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>
Showing 3 changed files with 37 additions and 6 deletions.
| @@ -305,17 +305,28 @@ func (m *model) resize(w, h int) tea.Cmd { | ||
| 305 | 305 | } else { |
| 306 | 306 | m.vp.Width = w |
| 307 | 307 | } |
| 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() | |
| 314 | 309 | m.vp.SetContent(m.tr.render()) |
| 315 | 310 | m.vp.GotoBottom() |
| 316 | 311 | return nil |
| 317 | 312 | } |
| 318 | 313 | |
| 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 | + | |
| 319 | 330 | // sync re-renders the scrollback, keeping the view pinned to the bottom unless |
| 320 | 331 | // the user has scrolled up to read something. |
| 321 | 332 | func (m *model) sync() tea.Cmd { |
| @@ -122,6 +122,7 @@ func (m *model) onModelKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) { | ||
| 122 | 122 | // which is the point of switching mid-session. |
| 123 | 123 | func (m *model) applyModel(id string) { |
| 124 | 124 | m.opts.Client.SetModel(id) |
| 125 | + m.refreshSplash() | |
| 125 | 126 | m.tr.addNotice("model: " + prettyModel(id) + " (" + id + ") — from the next turn on") |
| 126 | 127 | } |
| 127 | 128 | |
| @@ -323,3 +323,22 @@ func TestModelRowsShowTheContextWindow(t *testing.T) { | ||
| 323 | 323 | } |
| 324 | 324 | } |
| 325 | 325 | } |
| 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 | + } | |