From e380e168cd5ea88ae3ad0d789ccc9e88034c3261 Mon Sep 17 00:00:00 2001 From: John Coffey Date: Tue, 1 Sep 2026 10:53:28 -0700 Subject: [PATCH] Put "compose as new" where a thumb can find it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was never absent on a phone: it sits in the menu behind the ⋮ at the top of a message card. But that is not where anybody looks. On a phone you act on a thread from the strip at the bottom -- Reply, Reply all, Forward -- and what is not there is, for practical purposes, not there. The strip gets an overflow of its own, with compose-as-new in it. A fourth labelled button does not fit; this does, and it says what it is once opened. Measuring to place it turned up something else. Three labelled buttons want about 390px, and with the overflow rather more: enough for a 430px phone and not for a 390, 360 or 320 one. The strip was already over that line on the smaller ones before today, and simply overflowed. It now wraps, and the spacer that would push the overflow onto a line of its own is dropped on narrow screens so the buttons wrap as a group. Closes #181 --- web/src/styles/app.css | 8 +++++++- web/src/views/mail/ThreadView.tsx | 17 ++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/web/src/styles/app.css b/web/src/styles/app.css index 84b69a7..9f9630f 100644 --- a/web/src/styles/app.css +++ b/web/src/styles/app.css @@ -642,7 +642,10 @@ a.menu-item:hover { color: var(--fg); } .vcard-card { margin: 0 16px 12px; padding: 12px 16px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg-sunken); display: flex; align-items: center; gap: 12px; } .unsubscribe-row { margin: 0 16px 8px; font-size: .88em; color: var(--fg-muted); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } .reply-box { margin: 8px 16px 24px; } -.reply-box .reply-prompt { display: flex; gap: 8px; align-items: center; padding: 12px; border: 1px solid var(--border); border-radius: var(--radius); color: var(--fg-muted); } +/* Wraps, because it does not fit. Three labelled buttons and an overflow need + about 390px of it, which a 430px phone has and a 360px one does not -- and it + was already over the line on the smaller ones before the overflow was added. */ +.reply-box .reply-prompt { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; padding: 12px; border: 1px solid var(--border); border-radius: var(--radius); color: var(--fg-muted); } .reply-box .reply-prompt button { display: inline-flex; align-items: center; gap: 6px; padding: 8px 14px; border-radius: 999px; border: 1px solid var(--border-strong); color: var(--fg); font-weight: 500; } .reply-box .reply-prompt button:hover { background: var(--bg-hover); } .no-thread { height: 100%; display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 8px; color: var(--fg-muted); } @@ -1214,6 +1217,9 @@ button.dp-open:disabled { cursor: default; opacity: .5; } .dp-time:hover { background: var(--bg-hover); } .dp-time.selected { background: var(--accent); color: var(--accent-fg); } @media (max-width: 480px) { + /* The spacer would take the whole of the first line and push the overflow + onto a line of its own; packed together they wrap as a group instead. */ + .reply-box .reply-prompt .spacer { display: none; } .dp-datetime { flex-wrap: wrap; } .dp-datetime .dp-time-field { flex: 1 1 100%; } .dp-split { flex-direction: column; } diff --git a/web/src/views/mail/ThreadView.tsx b/web/src/views/mail/ThreadView.tsx index 365ad5a..df8bb5f 100644 --- a/web/src/views/mail/ThreadView.tsx +++ b/web/src/views/mail/ThreadView.tsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { AlertOctagon, Archive, ArrowLeft, ChevronDown, ChevronUp, FolderInput, Forward, Mail, MailOpen, MoreVertical, Printer, Reply, ReplyAll, ShieldCheck, Star, Tag, Trash2, Download } from "lucide-react"; +import { AlertOctagon, Archive, ArrowLeft, ChevronDown, ChevronUp, FolderInput, Forward, Mail, MailOpen, MailPlus, MoreVertical, Printer, Reply, ReplyAll, ShieldCheck, Star, Tag, Trash2, Download } from "lucide-react"; import { useMail } from "@/store/mail"; import { useSettings } from "@/store/settings"; import { useCompose } from "@/store/compose"; @@ -42,6 +42,8 @@ export function ThreadView({ threadId, mailboxId, onBack, actions, onNavigate, h const [allExpanded, setAllExpanded] = useState(false); const [labelAnchor, setLabelAnchor] = useState<{ x: number; y: number } | null>(null); const moreMenu = useMenu(); + /** The overflow on the reply strip, which is the only per-message menu a phone offers easily. */ + const replyMore = useMenu(); const scrollRef = useRef(null); const markTimer = useRef(null); const isTouch = useIsTouch(); @@ -289,6 +291,19 @@ export function ThreadView({ threadId, mailboxId, onBack, actions, onNavigate, h + {/* + On a phone this strip is where a thumb goes, and the per-message + menu at the top of a card is not somewhere anybody looks for + "send this again" -- which is how compose-as-new came to be + reported missing on mobile when it was there all along (#181). + A fourth full button does not fit at 500px; this does, and it + spells the action out once opened. + */} + + + + } label={t("Compose as new")} onClick={() => void useCompose.getState().composeAsNew(last)} /> + )}