diff --git a/internal/cmd/mail/draft.go b/internal/cmd/mail/draft.go
index 389b770..866a8a2 100644
--- a/internal/cmd/mail/draft.go
+++ b/internal/cmd/mail/draft.go
@@ -14,6 +14,7 @@ import (
"github.com/spf13/cobra"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/extension"
+ gmhtml "github.com/yuin/goldmark/renderer/html"
xhtml "golang.org/x/net/html"
gmailapi "github.com/open-cli-collective/google-cli/internal/api/gmail"
@@ -616,6 +617,9 @@ func renderMarkdown(src []byte) ([]byte, error) {
extension.Strikethrough,
extension.TaskList,
),
+ // A newline in an email body is a line break to its author (a two-line
+ // sign-off, an address), not a CommonMark soft break that joins lines.
+ goldmark.WithRendererOptions(gmhtml.WithHardWraps()),
)
var buf bytes.Buffer
if err := md.Convert(src, &buf); err != nil {
diff --git a/internal/cmd/mail/draft_test.go b/internal/cmd/mail/draft_test.go
index 5232127..b198927 100644
--- a/internal/cmd/mail/draft_test.go
+++ b/internal/cmd/mail/draft_test.go
@@ -979,3 +979,17 @@ func TestDraftCommand_ReplyTo_RejectsHeaderInjection(t *testing.T) {
testutil.Contains(t, err.Error(), "CR or LF")
})
}
+
+func TestRenderMarkdown_SingleNewlineIsALineBreak(t *testing.T) {
+ out, err := renderMarkdown([]byte("Hi Sam,\n\nThanks,\nAlex"))
+ if err != nil {
+ t.Fatalf("renderMarkdown: %v", err)
+ }
+ got := string(out)
+ if !strings.Contains(got, "Thanks,
\nAlex") && !strings.Contains(got, "Thanks,
\nAlex") {
+ t.Errorf("sign-off lines were joined: %q", got)
+ }
+ if strings.Count(got, "
") != 2 { + t.Errorf("blank line should still separate paragraphs: %q", got) + } +}