fix: Ghostty image hyperlink bug (#1297)

Drew Smirnoff created

## What?

Emited newlines outside `hyperlink()` call, link sits on single line and
works for Ghostty. And a small patch to fix split view rendering images,
when rendering was turned off.

## Why?

Ghostty terminates hyperlink region at newline boundary, so clickable
area was lost. Kitty/WezTerm carry URI across line breaks → worked
there.

Fixes #1291

---------

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

Change summary

main.go             |  2 ++
tui/folder_inbox.go | 11 ++++++++++-
view/html.go        |  2 +-
3 files changed, 13 insertions(+), 2 deletions(-)

Detailed changes

main.go 🔗

@@ -495,6 +495,7 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		m.folderInbox.SetDateFormat(m.config.GetDateFormat())
 		m.folderInbox.SetDetailedDates(m.config.EnableDetailedDates)
 		m.folderInbox.SetDefaultThreaded(m.config.EnableThreaded)
+		m.folderInbox.SetDisableImages(m.config.DisableImages)
 		// Use cached INBOX emails for instant display (memory first, then disk)
 		if cached, ok := m.folderEmails["INBOX"]; ok && len(cached) > 0 {
 			m.folderInbox.SetEmails(cached, m.config.Accounts)
@@ -1092,6 +1093,7 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 			m.folderInbox.SetDateFormat(m.config.GetDateFormat())
 			m.folderInbox.SetDetailedDates(m.config.EnableDetailedDates)
 			m.folderInbox.SetDefaultThreaded(m.config.EnableThreaded)
+			m.folderInbox.SetDisableImages(m.config.DisableImages)
 		}
 		return m, nil
 

tui/folder_inbox.go 🔗

@@ -97,6 +97,9 @@ type FolderInbox struct {
 	moveAccountID    string
 	moveSourceFolder string
 
+	// Image rendering preference, propagated from config.
+	disableImages bool
+
 	// Split pane state
 	previewPane        *EmailView
 	previewedUID       uint32
@@ -147,6 +150,12 @@ func (m *FolderInbox) SetDefaultThreaded(v bool) {
 	}
 }
 
+// SetDisableImages propagates the global image-display preference. Affects
+// future split-view previews; an already-open preview keeps its current state.
+func (m *FolderInbox) SetDisableImages(v bool) {
+	m.disableImages = v
+}
+
 // NewFolderInbox creates a new FolderInbox with the given folders and accounts.
 func NewFolderInbox(folders []string, accounts []config.Account) *FolderInbox {
 	folders = sortFolders(folders)
@@ -366,7 +375,7 @@ func (m *FolderInbox) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		previewWidth := m.calculatePreviewWidth()
 		inboxWidth := m.calculateInboxWidth()
 		colOffset := sidebarWidth + 2 + inboxWidth + 2 // borders + padding
-		m.previewPane = NewEmailViewPreview(*email, previewWidth, m.height, colOffset, false)
+		m.previewPane = NewEmailViewPreview(*email, previewWidth, m.height, colOffset, m.disableImages)
 		return m, nil
 	}
 

view/html.go 🔗

@@ -806,7 +806,7 @@ func renderHTMLToText(htmlBody []byte, inline map[string]string, h1Style, h2Styl
 				debugImageProtocol("no payload for src=%s", src)
 			}
 			if hyperlinkSupported() {
-				text.WriteString(hyperlink(src, fmt.Sprintf("\n [Click here to view image: %s] \n", alt)))
+				text.WriteString(fmt.Sprintf("\n %s \n", hyperlink(src, fmt.Sprintf("[Click here to view image: %s]", alt))))
 			} else {
 				text.WriteString(fmt.Sprintf("\n %s \n", linkStyle().Render(fmt.Sprintf("[Image: %s, %s]", alt, src))))
 			}