diff --git a/tui/composer.go b/tui/composer.go index b094f4d..af20941 100644 --- a/tui/composer.go +++ b/tui/composer.go @@ -193,6 +193,25 @@ func (m *Composer) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.subjectInput.SetWidth(inputWidth) m.bodyInput.SetWidth(inputWidth) m.signatureInput.SetWidth(inputWidth) + if msg.Height > 0 { + // Fixed rows: title, from, to, cc, bcc, subject, sig label, + // attachment, smime, button, blank, tip, help = 13 + const fixedRows = 13 + available := msg.Height - fixedRows + if available < 6 { + available = 6 + } + bodyHeight := (available * 55) / 100 + sigHeight := (available * 15) / 100 + if bodyHeight < 3 { + bodyHeight = 3 + } + if sigHeight < 2 { + sigHeight = 2 + } + m.bodyInput.SetHeight(bodyHeight) + m.signatureInput.SetHeight(sigHeight) + } case FileSelectedMsg: // Avoid duplicates diff --git a/tui/composer_test.go b/tui/composer_test.go index 17bb00d..ba011d8 100644 --- a/tui/composer_test.go +++ b/tui/composer_test.go @@ -286,3 +286,38 @@ func TestComposerSetSelectedAccount(t *testing.T) { t.Errorf("Expected selectedAccountIdx to remain 2, got %d", composer.selectedAccountIdx) } } + +// TestComposerDynamicHeight verifies that window resize updates textarea heights. +func TestComposerDynamicHeight(t *testing.T) { + composer := NewComposer("", "", "", "", false) + + model, _ := composer.Update(tea.WindowSizeMsg{Width: 120, Height: 40}) + composer = model.(*Composer) + + if composer.height != 40 { + t.Errorf("Expected height 40, got %d", composer.height) + } + + bodyH := composer.bodyInput.Height() + sigH := composer.signatureInput.Height() + + if bodyH <= 3 { + t.Errorf("Expected bodyInput height > 3, got %d", bodyH) + } + if sigH <= 1 { + t.Errorf("Expected signatureInput height > 1, got %d", sigH) + } + if bodyH <= sigH { + t.Errorf("Expected bodyInput height (%d) > signatureInput height (%d)", bodyH, sigH) + } + + // Small window: heights should not go below minimums + model, _ = composer.Update(tea.WindowSizeMsg{Width: 80, Height: 10}) + composer = model.(*Composer) + if composer.bodyInput.Height() < 3 { + t.Errorf("bodyInput height should be at least 3, got %d", composer.bodyInput.Height()) + } + if composer.signatureInput.Height() < 2 { + t.Errorf("signatureInput height should be at least 2, got %d", composer.signatureInput.Height()) + } +} diff --git a/tui/inbox.go b/tui/inbox.go index 957dd47..9d123d6 100644 --- a/tui/inbox.go +++ b/tui/inbox.go @@ -24,7 +24,7 @@ var ( tabBarStyle = lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()).BorderBottom(true).PaddingBottom(1).MarginBottom(1) ) -var dateStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("243")) +var dateStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("243")) var senderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("250")).Bold(true) type item struct { @@ -184,23 +184,23 @@ type AccountTab struct { } type Inbox struct { - list list.Model - isFetching bool - isRefreshing bool - emailsCount int - accounts []config.Account - emailsByAccount map[string][]fetcher.Email - allEmails []fetcher.Email - tabs []AccountTab - activeTabIndex int - width int - height int - currentAccountID string // Empty means "ALL" - emailCountByAcct map[string]int - mailbox MailboxKind - folderName string // Custom folder name override for title - noMoreByAccount map[string]bool // Per-account: true when pagination returns 0 results - extraShortHelpKeys []key.Binding + list list.Model + isFetching bool + isRefreshing bool + emailsCount int + accounts []config.Account + emailsByAccount map[string][]fetcher.Email + allEmails []fetcher.Email + tabs []AccountTab + activeTabIndex int + width int + height int + currentAccountID string // Empty means "ALL" + emailCountByAcct map[string]int + mailbox MailboxKind + folderName string // Custom folder name override for title + noMoreByAccount map[string]bool // Per-account: true when pagination returns 0 results + extraShortHelpKeys []key.Binding } func NewInbox(emails []fetcher.Email, accounts []config.Account) *Inbox {