fix(inbox): reply quote no longer overflows or single-lines (#165)

A long reply quote was forcing the entire 3-panel inbox layout
wider, pushing the contact sidebar off-screen and making the page
horizontally scrollable. Two root causes (both correctly diagnosed
in the issue by @tekrexllp):

1. ReplyQuote applied `truncate` to the preview text, which sets
   `white-space: nowrap`. Long previews became one impossibly-wide
   line instead of wrapping.

2. The flex chain from MessageBubble outward (MessageActions →
   middle-panel wrapper → page) had no `min-w-0` on its flex
   children. Default `min-width: auto` means a flex child grows to
   its content's natural width, so the long quote propagated all the
   way up to the inbox-page flex container and pushed the contact
   sidebar past the viewport.

Fix:
  - ReplyQuote preview now uses `whitespace-pre-wrap break-words`
    instead of `truncate`. The author label stays single-line (it's
    a name, ellipsis is right for it). `overflow-hidden` on the
    inner text container as belt-and-braces.
  - MessageActions' .group/actions wrapper gets `min-w-0` so the
    `max-w-[75%]` cap actually holds even with wide content.
  - inbox/page.tsx middle-panel wrapper gets `min-w-0` so the
    MessageThread can shrink below its natural content width when
    the conversation list and contact sidebar are also flexing.

Audited the other 38 `truncate` usages across the codebase —
they're all on intentionally single-line content (contact names,
file paths, last-message previews in lists, headers, node
summaries) where ellipsis is the right behavior. ReplyQuote was
the only multi-line `truncate` misuse.

Reported and diagnosed by @tekrexllp in #165.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Arnas Donauskas 2026-05-31 12:28:49 +03:00
parent 1b66431acf
commit 13bafa5747
No known key found for this signature in database
3 changed files with 25 additions and 5 deletions

View File

@ -530,10 +530,16 @@ export default function InboxPage() {
{/* Center panel: Message thread.
Hidden on mobile when no conversation is selected so the
list can occupy the full width. Always visible on lg+
(shows its own empty-state if no thread is picked yet). */}
(shows its own empty-state if no thread is picked yet).
`min-w-0` is load-bearing: without it, a single wide piece
of content inside the thread (long quote preview, very
long URL in a message body) forces the flex child past
its share and pushes the contact-sidebar panel off-screen
on the right. Issue #165. */}
<div
className={cn(
"flex h-full flex-1 lg:flex",
"flex h-full min-w-0 flex-1 lg:flex",
hasActiveConv ? "flex" : "hidden lg:flex",
)}
>

View File

@ -85,7 +85,12 @@ export function MessageActions({
onContextMenu={handleContextMenu}
onBlur={() => setTouchOpen(false)}
>
<div className="group/actions relative max-w-[75%]">
{/* `min-w-0` lets this flex child actually respect the 75% cap.
* Default `min-width: auto` lets content (a long quote preview,
* an unbroken URL) push past the cap and shove the row past
* 100%, which used to bleed across into the contact-sidebar
* area. See issue #165. */}
<div className="group/actions relative min-w-0 max-w-[75%]">
{children}
<div
data-touch-open={touchOpen || pickerOpen ? "true" : undefined}

View File

@ -31,11 +31,20 @@ export function ReplyQuote({
: "mb-1.5 rounded-md bg-black/20",
)}
>
<div className="min-w-0 flex-1">
<div className="min-w-0 flex-1 overflow-hidden">
<div className="truncate text-[11px] font-medium text-primary">
{authorLabel}
</div>
<div className="truncate text-xs text-slate-200/80">{preview}</div>
{/* Wrap the preview instead of truncating to a single line.
* `truncate` (white-space: nowrap) forced the quote onto one
* impossibly-wide line and — because the parent flex chain
* lacked `min-w-0` at every step — pushed the entire inbox
* layout wider, shoving the contact sidebar off-screen.
* `break-words` also wraps long URLs that have no whitespace
* to break on. Issue #165. */}
<div className="whitespace-pre-wrap break-words text-xs text-slate-200/80">
{preview}
</div>
</div>
{onDismiss && (
<button