@@ -196,6 +196,11 @@ type UI struct {
197 isTransparent bool
197 isTransparent bool
199+ // themeKey identifies the currently applied theme so applyTheme can
200+ // skip the expensive style rebuild when switching to a provider that
201+ // resolves to the same theme.
199 focus uiFocusState
204 focus uiFocusState
200 state uiState
205 state uiState
@@ -380,6 +385,12 @@ func New(com *common.Common, initialSessionID string, continueLast bool) *UI {
381 status := NewStatus(com, ui)
386 status := NewStatus(com, ui)
388+ // Seed the active theme key from the large model provider so the
389+ // first model selection can correctly skip a redundant theme swap.
390+ if cfg := com.Config(); cfg != nil {
391+ ui.themeKey = styles.ThemeKeyForProvider(cfg.Models[config.SelectedModelTypeLarge].Provider)
383 ui.setEditorPrompt(com.Workspace.PermissionSkipRequests())
394 ui.setEditorPrompt(com.Workspace.PermissionSkipRequests())
384 ui.randomizePlaceholders()
395 ui.randomizePlaceholders()
385 ui.textarea.Placeholder = ui.readyPlaceholder
396 ui.textarea.Placeholder = ui.readyPlaceholder
@@ -1842,8 +1853,10 @@ func (m *UI) handleSelectModel(msg dialog.ActionSelectModel) tea.Cmd {
1842 } else {
1853 } else {
1843 if msg.ModelType == config.SelectedModelTypeLarge {
1854 if msg.ModelType == config.SelectedModelTypeLarge {
1844 // Swap the theme live based on the newly selected large
1855 // Swap the theme live based on the newly selected large
1845− // model's provider.
1856+ // model's provider. Skipped when the provider resolves to
1846− m.applyTheme(styles.ThemeForProvider(providerID))
1857+ // the already-active theme, which avoids a full markdown
1858+ // re-render of the transcript on every selection.
1859+ m.applyThemeForProvider(providerID)
1848 if _, ok := cfg.Models[config.SelectedModelTypeSmall]; !ok {
1861 if _, ok := cfg.Models[config.SelectedModelTypeSmall]; !ok {
1849 // Ensure small model is set is unset.
1862 // Ensure small model is set is unset.
@@ -3371,6 +3384,21 @@ func (m *UI) cacheSidebarLogo(width int) {
3371 m.sidebarLogo = renderLogo(m.com.Styles, true, m.com.IsHyper(), width)
3384 m.sidebarLogo = renderLogo(m.com.Styles, true, m.com.IsHyper(), width)
3387+// applyThemeForProvider swaps the active theme to the one associated with
3388+// the given provider, but only when that theme differs from the one
3389+// already applied. Most providers share a single theme, so re-selecting a
3390+// model from the same theme family would otherwise pay the full cost of
3391+// invalidating the markdown renderer cache and re-rendering the entire
3392+// transcript for no visible change.
3393+func (m *UI) applyThemeForProvider(providerID string) {
3394+ key := styles.ThemeKeyForProvider(providerID)
3395+ if key == m.themeKey {
3399+ m.applyTheme(styles.ThemeForProvider(providerID))
3374// applyTheme replaces the active styles with the given theme, drops the
3402// applyTheme replaces the active styles with the given theme, drops the
3375// shared markdown renderer cache, and refreshes every component that
3403// shared markdown renderer cache, and refreshes every component that
3376// caches style data.
3404// caches style data.