fix(tui): settings focus nav (#1367)

FromSi and drew created

## What?

Improves Settings pane focus navigation in the TUI.

- Adds `left` navigation back from the content pane to the settings
menu.
- Allows `left` to leave Settings from the menu, like `esc`.
- Hides right-pane cursor/highlight when the content pane is not
focused.
- Keeps bold styling only on the currently focused pane.
- Updates English Settings help text.
- Adds tests for horizontal pane focus and App Encryption input
behavior.

<img width="1400" height="800" alt="view"
src="https://github.com/user-attachments/assets/b35a6c89-0efa-4bb6-aad6-91fda01fb274"
/>

## Why?

Closes #1231

Fixes confusing Settings focus behavior where both panes could appear
selected at the same time.

This makes keyboard navigation clearer: `>` and bold styling now
indicate the active focus, while `left` provides a consistent way to
move back or exit.

---------

Signed-off-by: drew <me@andrinoff.com>
Co-authored-by: drew <me@andrinoff.com>

Change summary

i18n/locales/ar.json        |   2 
i18n/locales/de.json        |   2 
i18n/locales/en.json        |   2 
i18n/locales/es.json        |   2 
i18n/locales/fr.json        |   2 
i18n/locales/ja.json        |   2 
i18n/locales/pl.json        |   2 
i18n/locales/pt.json        |   2 
i18n/locales/ru.json        |   2 
i18n/locales/uk.json        |   2 
i18n/locales/zh.json        |   2 
tui/constants.go            |   1 
tui/navigation_wrap_test.go | 100 +++++++++++++++++++++++++++++++++++
tui/settings.go             | 109 ++++++++++++++++++++++++++++----------
tui/settings_accounts.go    |  18 ++----
tui/settings_crypto.go      |   6 +-
tui/settings_encryption.go  |  73 +++++++++++++++++--------
tui/settings_general.go     |   9 +--
tui/settings_lists.go       |  18 ++----
tui/settings_plugins.go     |  20 ++----
tui/settings_theme.go       |   9 +--
tui/theme.go                |   4 
22 files changed, 270 insertions(+), 119 deletions(-)

Detailed changes

i18n/locales/ar.json 🔗

@@ -123,7 +123,7 @@
       "category_encryption": "تشفير التطبيق",
       "category_plugins": "الإضافات",
       "help_menu": "↑/↓: التنقل • يمين/enter: اختيار • esc: رجوع",
-      "help_content": "esc: العودة للقائمة"
+      "help_content": "left/esc: العودة للقائمة"
     },
     "settings_accounts": {
       "title": "إعدادات الحسابات",

i18n/locales/de.json 🔗

@@ -121,7 +121,7 @@
       "category_encryption": "App-Verschlüsselung",
       "category_plugins": "Plugins",
       "help_menu": "↑/↓: navigieren • rechts/enter: auswählen • esc: zurück",
-      "help_content": "esc: zurück zum Menü"
+      "help_content": "left/esc: zurück zum Menü"
     },
     "settings_accounts": {
       "title": "Kontoeinstellungen",

i18n/locales/en.json 🔗

@@ -123,7 +123,7 @@
       "category_encryption": "App Encryption",
       "category_plugins": "Plugins",
       "help_menu": "↑/↓: navigate • right/enter: select • esc: go back",
-      "help_content": "esc: back to menu"
+      "help_content": "left/esc: back to menu"
     },
     "settings_accounts": {
       "title": "Account Settings",

i18n/locales/es.json 🔗

@@ -121,7 +121,7 @@
       "category_encryption": "Cifrado de Aplicación",
       "category_plugins": "Plugins",
       "help_menu": "↑/↓: navegar • derecha/enter: seleccionar • esc: volver",
-      "help_content": "esc: volver al menú"
+      "help_content": "left/esc: volver al menú"
     },
     "settings_accounts": {
       "title": "Configuración de Cuentas",

i18n/locales/fr.json 🔗

@@ -121,7 +121,7 @@
       "category_encryption": "Chiffrement de l'Application",
       "category_plugins": "Plugins",
       "help_menu": "↑/↓: naviguer • droite/entrée: sélectionner • esc: retour",
-      "help_content": "esc: retour au menu"
+      "help_content": "left/esc: retour au menu"
     },
     "settings_accounts": {
       "title": "Paramètres des Comptes",

i18n/locales/ja.json 🔗

@@ -120,7 +120,7 @@
       "category_encryption": "アプリの暗号化",
       "category_plugins": "プラグイン",
       "help_menu": "↑/↓: 移動 • 右/enter: 選択 • esc: 戻る",
-      "help_content": "esc: メニューに戻る"
+      "help_content": "left/esc: メニューに戻る"
     },
     "settings_accounts": {
       "title": "アカウント設定",

i18n/locales/pl.json 🔗

@@ -123,7 +123,7 @@
       "category_encryption": "Szyfrowanie Aplikacji",
       "category_plugins": "Wtyczki",
       "help_menu": "↑/↓: nawigacja • prawo/enter: wybierz • esc: wstecz",
-      "help_content": "esc: powrót do menu"
+      "help_content": "left/esc: powrót do menu"
     },
     "settings_accounts": {
       "title": "Ustawienia Kont",

i18n/locales/pt.json 🔗

@@ -121,7 +121,7 @@
       "category_encryption": "Criptografia do Aplicativo",
       "category_plugins": "Plugins",
       "help_menu": "↑/↓: navegar • direita/enter: selecionar • esc: voltar",
-      "help_content": "esc: voltar ao menu"
+      "help_content": "left/esc: voltar ao menu"
     },
     "settings_accounts": {
       "title": "Configurações de Contas",

i18n/locales/ru.json 🔗

@@ -123,7 +123,7 @@
       "category_encryption": "Шифрование Приложения",
       "category_plugins": "Плагины",
       "help_menu": "↑/↓: навигация • вправо/enter: выбор • esc: назад",
-      "help_content": "esc: назад в меню"
+      "help_content": "left/esc: назад в меню"
     },
     "settings_accounts": {
       "title": "Настройки Учётных Записей",

i18n/locales/uk.json 🔗

@@ -122,7 +122,7 @@
       "category_encryption": "Шифрування додатка",
       "category_plugins": "Плагіни",
       "help_menu": "↑/↓: навігація • right/enter: вибрати • esc: назад",
-      "help_content": "esc: назад до меню"
+      "help_content": "left/esc: назад до меню"
     },
     "settings_accounts": {
       "title": "Налаштування облікових записів",

i18n/locales/zh.json 🔗

@@ -120,7 +120,7 @@
       "category_encryption": "应用加密",
       "category_plugins": "插件",
       "help_menu": "↑/↓: 导航 • 右/enter: 选择 • esc: 返回",
-      "help_content": "esc: 返回菜单"
+      "help_content": "left/esc: 返回菜单"
     },
     "settings_accounts": {
       "title": "账户设置",

tui/constants.go 🔗

@@ -3,6 +3,7 @@ package tui
 const (
 	keyEnter    = "enter"
 	keyDown     = "down"
+	keyLeft     = "left"
 	keyRight    = "right"
 	keyCount    = "count"
 	keyINBOX    = "INBOX"

tui/navigation_wrap_test.go 🔗

@@ -109,6 +109,106 @@ func TestSettingsNavigationWraps(t *testing.T) {
 	})
 }
 
+func TestSettingsHorizontalPaneFocus(t *testing.T) {
+	t.Run("right moves focus from menu to content", func(t *testing.T) {
+		settings := NewSettings(&config.Config{})
+		settings.activePane = PaneMenu
+		settings.menuCursor = int(CategoryGeneral)
+
+		model, _ := settings.Update(tea.KeyPressMsg{Code: tea.KeyRight})
+		settings = model.(*Settings)
+
+		if settings.activePane != PaneContent {
+			t.Fatalf("right from menu pane should focus content, got %d", settings.activePane)
+		}
+	})
+
+	t.Run("esc moves focus from content to menu", func(t *testing.T) {
+		settings := NewSettings(&config.Config{})
+		settings.activePane = PaneContent
+		settings.activeCategory = CategoryGeneral
+		settings.menuCursor = int(CategoryGeneral)
+
+		model, _ := settings.Update(tea.KeyPressMsg{Code: tea.KeyEsc})
+		settings = model.(*Settings)
+
+		if settings.activePane != PaneMenu {
+			t.Fatalf("esc from content pane should focus menu, got %d", settings.activePane)
+		}
+	})
+
+	t.Run("left moves focus from content to menu", func(t *testing.T) {
+		settings := NewSettings(&config.Config{})
+		settings.activePane = PaneContent
+		settings.activeCategory = CategoryGeneral
+		settings.menuCursor = int(CategoryGeneral)
+
+		model, _ := settings.Update(tea.KeyPressMsg{Code: tea.KeyLeft})
+		settings = model.(*Settings)
+
+		if settings.activePane != PaneMenu {
+			t.Fatalf("left from content pane should focus menu, got %d", settings.activePane)
+		}
+	})
+
+	t.Run("left does not exit settings from menu", func(t *testing.T) {
+		settings := NewSettings(&config.Config{})
+		settings.activePane = PaneMenu
+
+		model, cmd := settings.Update(tea.KeyPressMsg{Code: tea.KeyLeft})
+		settings = model.(*Settings)
+
+		if cmd != nil {
+			t.Fatal("left from menu pane should not return to choice menu")
+		}
+		if settings.activePane != PaneMenu {
+			t.Fatalf("left from menu pane should keep menu focused, got %d", settings.activePane)
+		}
+	})
+}
+
+func TestSettingsEncryptionLeftKeyInInput(t *testing.T) {
+	t.Run("at input start returns to menu", func(t *testing.T) {
+		settings := NewSettings(&config.Config{})
+		settings.activePane = PaneContent
+		settings.activeCategory = CategoryEncryption
+		settings.encFocusIndex = 0
+		settings.encPasswordInput.SetValue("secret")
+		settings.encPasswordInput.SetCursor(0)
+		settings.encPasswordInput.Focus()
+
+		model, _ := settings.Update(tea.KeyPressMsg{Code: tea.KeyLeft})
+		settings = model.(*Settings)
+
+		if settings.activePane != PaneMenu {
+			t.Fatalf("left at start of encryption input should focus menu, got %d", settings.activePane)
+		}
+		if settings.encPasswordInput.Value() != "" {
+			t.Fatal("left at start of encryption input should clear input like esc")
+		}
+	})
+
+	t.Run("inside input moves cursor", func(t *testing.T) {
+		settings := NewSettings(&config.Config{})
+		settings.activePane = PaneContent
+		settings.activeCategory = CategoryEncryption
+		settings.encFocusIndex = 0
+		settings.encPasswordInput.SetValue("secret")
+		settings.encPasswordInput.SetCursor(1)
+		settings.encPasswordInput.Focus()
+
+		model, _ := settings.Update(tea.KeyPressMsg{Code: tea.KeyLeft})
+		settings = model.(*Settings)
+
+		if settings.activePane != PaneContent {
+			t.Fatalf("left inside encryption input should keep content focused, got %d", settings.activePane)
+		}
+		if settings.encPasswordInput.Position() != 0 {
+			t.Fatalf("left inside encryption input should move cursor left, got position %d", settings.encPasswordInput.Position())
+		}
+	})
+}
+
 func TestFilePickerNavigationWraps(t *testing.T) {
 	dir := t.TempDir()
 	if err := os.WriteFile(filepath.Join(dir, "a.txt"), []byte("a"), 0o600); err != nil {

tui/settings.go 🔗

@@ -194,34 +194,7 @@ func (m *Settings) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		return m, nil
 
 	case tea.KeyPressMsg:
-		// Global shortcut to return to menu from content pane
-		if m.activePane == PaneContent && msg.String() == "esc" {
-			// unless we are in crypto config or encryption editing which have their own esc logic
-			if (m.activeCategory != CategoryAccounts || !m.isCryptoConfig) &&
-				(m.activeCategory != CategoryEncryption || m.encFocusIndex <= -1) &&
-				(m.activeCategory != CategoryPlugins || (!m.pluginEditing && m.pluginSelected == "")) {
-				m.activePane = PaneMenu
-				return m, nil
-			}
-		}
-
-		if m.activePane == PaneMenu {
-			return m.updateMenu(msg)
-		}
-		switch m.activeCategory {
-		case CategoryGeneral:
-			return m.updateGeneral(msg)
-		case CategoryAccounts:
-			return m.updateAccounts(msg)
-		case CategoryTheme:
-			return m.updateTheme(msg)
-		case CategoryMailingLists:
-			return m.updateMailingLists(msg)
-		case CategoryEncryption:
-			return m.updateEncryption(msg)
-		case CategoryPlugins:
-			return m.updatePlugins(msg)
-		}
+		return m.updateKeyPress(msg)
 
 	case SecureModeEnabledMsg:
 		m.encEnabling = false
@@ -270,6 +243,80 @@ func (m *Settings) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	return m, tea.Batch(cmds...)
 }
 
+func (m *Settings) updateKeyPress(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
+	// Global shortcut to return to menu from content pane
+	if m.activePane == PaneContent && msg.String() == "esc" {
+		// unless we are in crypto config or encryption editing which have their own esc logic
+		if (m.activeCategory != CategoryAccounts || !m.isCryptoConfig) &&
+			(m.activeCategory != CategoryEncryption || m.encFocusIndex <= -1) &&
+			(m.activeCategory != CategoryPlugins || (!m.pluginEditing && m.pluginSelected == "")) {
+			m.activePane = PaneMenu
+			return m, nil
+		}
+	}
+
+	if m.activePane == PaneContent && msg.String() == keyLeft && m.canFocusSettingsMenuWithLeft() {
+		m.activePane = PaneMenu
+		return m, nil
+	}
+
+	if m.activePane == PaneMenu {
+		return m.updateMenu(msg)
+	}
+	switch m.activeCategory {
+	case CategoryGeneral:
+		return m.updateGeneral(msg)
+	case CategoryAccounts:
+		return m.updateAccounts(msg)
+	case CategoryTheme:
+		return m.updateTheme(msg)
+	case CategoryMailingLists:
+		return m.updateMailingLists(msg)
+	case CategoryEncryption:
+		return m.updateEncryption(msg)
+	case CategoryPlugins:
+		return m.updatePlugins(msg)
+	}
+
+	return m, nil
+}
+
+func (m *Settings) canFocusSettingsMenuWithLeft() bool {
+	switch m.activeCategory {
+	case CategoryAccounts:
+		return !m.isCryptoConfig && !m.confirmingDelete
+	case CategoryEncryption:
+		return config.IsSecureModeEnabled() && !m.confirmingDisable
+	case CategoryPlugins:
+		return !m.pluginEditing && m.pluginSelected == ""
+	case CategoryGeneral, CategoryTheme, CategoryMailingLists:
+		return true
+	default:
+		return true
+	}
+}
+
+func (m *Settings) contentItemStyle(selected bool) lipgloss.Style {
+	if selected && m.activePane == PaneContent {
+		return selectedAccountItemStyle
+	}
+	return accountItemStyle
+}
+
+func (m *Settings) contentCursor(selected bool) string {
+	if selected && m.activePane == PaneContent {
+		return "> "
+	}
+	return "  "
+}
+
+func (m *Settings) contentFocusStyle() lipgloss.Style {
+	if m.activePane == PaneContent {
+		return settingsFocusedStyle
+	}
+	return settingsBlurredStyle
+}
+
 func (m *Settings) updateMenu(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
 	categoryCount := int(CategoryPlugins) + 1
 
@@ -340,7 +387,11 @@ func (m *Settings) View() tea.View {
 
 		style := accountItemStyle
 		if m.menuCursor == i {
-			style = selectedAccountItemStyle
+			if m.activePane == PaneMenu {
+				style = selectedAccountItemStyle
+			} else {
+				style = selectedAccountItemStyle.UnsetBold()
+			}
 		}
 
 		left.WriteString(style.Render(cursor+c) + "\n")

tui/settings_accounts.go 🔗

@@ -153,23 +153,17 @@ func (m *Settings) viewAccounts() string {
 
 		line := fmt.Sprintf("%s - %s", displayName, accountEmailStyle.Render(providerInfo))
 
-		cursor := "  "
-		style := accountItemStyle
-		if m.accountsCursor == i {
-			cursor = "> "
-			style = selectedAccountItemStyle
-		}
+		selected := m.accountsCursor == i
+		cursor := m.contentCursor(selected)
+		style := m.contentItemStyle(selected)
 
 		b.WriteString(style.Render(cursor+line) + "\n")
 	}
 
 	// Add Account option
-	cursor := "  "
-	style := accountItemStyle
-	if m.accountsCursor == len(m.cfg.Accounts) {
-		cursor = "> "
-		style = selectedAccountItemStyle
-	}
+	selected := m.accountsCursor == len(m.cfg.Accounts)
+	cursor := m.contentCursor(selected)
+	style := m.contentItemStyle(selected)
 	b.WriteString(style.Render(cursor+t("settings_accounts.add_account")) + "\n\n")
 
 	b.WriteString(helpStyle.Render(t("settings_accounts.help")))

tui/settings_crypto.go 🔗

@@ -121,7 +121,7 @@ func (m *Settings) viewSMIMEConfig() string {
 
 	renderField := func(index int, label, content string) {
 		if m.cryptoFocusIndex == index {
-			b.WriteString(settingsFocusedStyle.Render(label) + "\n")
+			b.WriteString(m.contentFocusStyle().Render(label) + "\n")
 		} else {
 			b.WriteString(settingsBlurredStyle.Render(label) + "\n")
 		}
@@ -162,12 +162,12 @@ func (m *Settings) viewSMIMEConfig() string {
 	saveBtn := "[ Save ]"
 	cancelBtn := "[ Cancel ]"
 	if m.cryptoFocusIndex == 8 {
-		saveBtn = settingsFocusedStyle.Render(saveBtn)
+		saveBtn = m.contentFocusStyle().Render(saveBtn)
 	} else {
 		saveBtn = settingsBlurredStyle.Render(saveBtn)
 	}
 	if m.cryptoFocusIndex == 9 {
-		cancelBtn = settingsFocusedStyle.Render(cancelBtn)
+		cancelBtn = m.contentFocusStyle().Render(cancelBtn)
 	} else {
 		cancelBtn = settingsBlurredStyle.Render(cancelBtn)
 	}

tui/settings_encryption.go 🔗

@@ -36,15 +36,14 @@ func (m *Settings) updateEncryption(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
 
 	switch msg.String() {
 	case "esc":
-		// Clear inputs and return to menu
-		m.encPasswordInput.SetValue("")
-		m.encConfirmInput.SetValue("")
-		m.encPasswordStrength = ""
-		m.encPasswordInput.Blur()
-		m.encConfirmInput.Blur()
-		m.encError = ""
-		m.activePane = PaneMenu
+		m.leaveEncryptionSettings()
 		return m, nil
+	case keyLeft:
+		if m.encryptionInputCursorAtStart() {
+			m.leaveEncryptionSettings()
+			return m, nil
+		}
+		return m.updateFocusedEncryptionInput(msg)
 	case "tab", keyShiftTab, keyDown, "up":
 		if msg.String() == keyShiftTab || msg.String() == "up" {
 			m.encFocusIndex--
@@ -97,23 +96,47 @@ func (m *Settings) updateEncryption(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
 			}
 		}
 	default:
-		// Forward input to focused textinput
-		var cmd tea.Cmd
-		switch m.encFocusIndex {
-		case 0:
-			before := m.encPasswordInput.Value()
-			m.encPasswordInput, cmd = m.encPasswordInput.Update(msg)
-			if m.encPasswordInput.Value() != before {
-				m.handlePasswordChanged()
-			}
-		case 1:
-			m.encConfirmInput, cmd = m.encConfirmInput.Update(msg)
-		}
-		return m, cmd
+		return m.updateFocusedEncryptionInput(msg)
 	}
 	return m, nil
 }
 
+func (m *Settings) encryptionInputCursorAtStart() bool {
+	switch m.encFocusIndex {
+	case 0:
+		return m.encPasswordInput.Position() == 0
+	case 1:
+		return m.encConfirmInput.Position() == 0
+	default:
+		return false
+	}
+}
+
+func (m *Settings) leaveEncryptionSettings() {
+	m.encPasswordInput.SetValue("")
+	m.encConfirmInput.SetValue("")
+	m.encPasswordStrength = ""
+	m.encPasswordInput.Blur()
+	m.encConfirmInput.Blur()
+	m.encError = ""
+	m.activePane = PaneMenu
+}
+
+func (m *Settings) updateFocusedEncryptionInput(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
+	var cmd tea.Cmd
+	switch m.encFocusIndex {
+	case 0:
+		before := m.encPasswordInput.Value()
+		m.encPasswordInput, cmd = m.encPasswordInput.Update(msg)
+		if m.encPasswordInput.Value() != before {
+			m.handlePasswordChanged()
+		}
+	case 1:
+		m.encConfirmInput, cmd = m.encConfirmInput.Update(msg)
+	}
+	return m, cmd
+}
+
 func (m *Settings) viewEncryption() string {
 	var b strings.Builder
 	isEnabled := config.IsSecureModeEnabled()
@@ -131,7 +154,7 @@ func (m *Settings) viewEncryption() string {
 			)
 			b.WriteString(dialog + "\n")
 		} else {
-			b.WriteString(settingsFocusedStyle.Render("  "+t("settings_encryption.enabled")) + "\n\n")
+			b.WriteString(m.contentFocusStyle().Render("  "+t("settings_encryption.enabled")) + "\n\n")
 			b.WriteString(accountEmailStyle.Render("  "+t("settings_encryption.disable_button")) + "\n\n")
 			b.WriteString(helpStyle.Render("enter: disable"))
 		}
@@ -139,7 +162,7 @@ func (m *Settings) viewEncryption() string {
 		b.WriteString(accountEmailStyle.Render(t("settings_encryption.disabled")) + "\n\n")
 
 		if m.encFocusIndex == 0 {
-			b.WriteString(settingsFocusedStyle.Render(t("settings_encryption.password_label") + "\n"))
+			b.WriteString(m.contentFocusStyle().Render(t("settings_encryption.password_label") + "\n"))
 		} else {
 			b.WriteString(settingsBlurredStyle.Render(t("settings_encryption.password_label") + "\n"))
 		}
@@ -149,7 +172,7 @@ func (m *Settings) viewEncryption() string {
 		}
 
 		if m.encFocusIndex == 1 {
-			b.WriteString(settingsFocusedStyle.Render(t("settings_encryption.confirm_label") + "\n"))
+			b.WriteString(m.contentFocusStyle().Render(t("settings_encryption.confirm_label") + "\n"))
 		} else {
 			b.WriteString(settingsBlurredStyle.Render(t("settings_encryption.confirm_label") + "\n"))
 		}
@@ -161,7 +184,7 @@ func (m *Settings) viewEncryption() string {
 
 		saveBtn := "[ " + t("settings_encryption.enable_button") + " ]"
 		if m.encFocusIndex == 2 {
-			b.WriteString(settingsFocusedStyle.Render(saveBtn) + "\n")
+			b.WriteString(m.contentFocusStyle().Render(saveBtn) + "\n")
 		} else {
 			b.WriteString(settingsBlurredStyle.Render(saveBtn) + "\n")
 		}

tui/settings_general.go 🔗

@@ -130,12 +130,9 @@ func (m *Settings) viewGeneral() string {
 	options := m.buildGeneralOptions()
 
 	for i, opt := range options {
-		cursor := "  "
-		style := accountItemStyle
-		if m.generalCursor == i {
-			cursor = "> "
-			style = selectedAccountItemStyle
-		}
+		selected := m.generalCursor == i
+		cursor := m.contentCursor(selected)
+		style := m.contentItemStyle(selected)
 
 		label := t(opt.labelKey)
 		text := fmt.Sprintf("%s: %s", label, opt.value)

tui/settings_lists.go 🔗

@@ -74,21 +74,15 @@ func (m *Settings) viewMailingLists() string {
 		})
 		line := fmt.Sprintf("%s - %s", list.Name, accountEmailStyle.Render(addrCount))
 
-		cursor := "  "
-		style := accountItemStyle
-		if m.listsCursor == i {
-			cursor = "> "
-			style = selectedAccountItemStyle
-		}
+		selected := m.listsCursor == i
+		cursor := m.contentCursor(selected)
+		style := m.contentItemStyle(selected)
 		b.WriteString(style.Render(cursor+line) + "\n")
 	}
 
-	cursor := "  "
-	style := accountItemStyle
-	if m.listsCursor == len(m.cfg.MailingLists) {
-		cursor = "> "
-		style = selectedAccountItemStyle
-	}
+	selected := m.listsCursor == len(m.cfg.MailingLists)
+	cursor := m.contentCursor(selected)
+	style := m.contentItemStyle(selected)
 	b.WriteString(style.Render(cursor+t("settings_mailing_lists.add_list")) + "\n\n")
 
 	b.WriteString(helpStyle.Render(t("settings_mailing_lists.help")))

tui/settings_plugins.go 🔗

@@ -156,12 +156,9 @@ func (m *Settings) viewPlugins() string {
 		}
 
 		for i, s := range schemas {
-			cursor := "  "
-			style := accountItemStyle
-			if m.pluginListCursor == i {
-				cursor = "> "
-				style = selectedAccountItemStyle
-			}
+			selected := m.pluginListCursor == i
+			cursor := m.contentCursor(selected)
+			style := m.contentItemStyle(selected)
 			line := fmt.Sprintf("%s (%d %s)", s.Plugin, len(s.Defs), pluralSettings(len(s.Defs)))
 			b.WriteString(style.Render(cursor+line) + "\n")
 		}
@@ -174,12 +171,9 @@ func (m *Settings) viewPlugins() string {
 	b.WriteString(accountEmailStyle.Render(m.pluginSelected) + "\n\n")
 
 	for i, def := range defs {
-		cursor := "  "
-		style := accountItemStyle
-		if m.pluginSettingCursor == i {
-			cursor = "> "
-			style = selectedAccountItemStyle
-		}
+		selected := m.pluginSettingCursor == i
+		cursor := m.contentCursor(selected)
+		style := m.contentItemStyle(selected)
 
 		label := def.Label
 		if label == "" {
@@ -193,7 +187,7 @@ func (m *Settings) viewPlugins() string {
 
 	if m.pluginEditing {
 		b.WriteString("\n")
-		b.WriteString(settingsFocusedStyle.Render("Edit "+m.pluginEditingKey) + "\n")
+		b.WriteString(m.contentFocusStyle().Render("Edit "+m.pluginEditingKey) + "\n")
 		b.WriteString(m.pluginInput.View() + "\n")
 		b.WriteString("\n")
 		b.WriteString(helpStyle.Render("enter save • esc cancel"))

tui/settings_theme.go 🔗

@@ -46,12 +46,9 @@ func (m *Settings) viewTheme() string {
 			label += " (" + t("settings_theme.current") + ")"
 		}
 
-		cursor := "  "
-		style := accountItemStyle
-		if m.themeCursor == i {
-			cursor = "> "
-			style = selectedAccountItemStyle
-		}
+		selected := m.themeCursor == i
+		cursor := m.contentCursor(selected)
+		style := m.contentItemStyle(selected)
 
 		b.WriteString(style.Render(cursor+label) + "\n")
 	}

tui/theme.go 🔗

@@ -47,10 +47,10 @@ func RebuildStyles() {
 
 	// settings.go
 	accountItemStyle = lipgloss.NewStyle().PaddingLeft(2)
-	selectedAccountItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(t.Accent)
+	selectedAccountItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(t.Accent).Bold(true)
 	accountEmailStyle = lipgloss.NewStyle().Foreground(t.Secondary)
 	dangerStyle = lipgloss.NewStyle().Foreground(t.Danger)
-	settingsFocusedStyle = lipgloss.NewStyle().Foreground(t.Accent)
+	settingsFocusedStyle = lipgloss.NewStyle().Foreground(t.Accent).Bold(true)
 	settingsBlurredStyle = lipgloss.NewStyle().Foreground(t.Secondary)
 
 	// composer.go