Files
buzz/desktop/src/features/messages/ui/MessageHeader.tsx
cynfria cec5c8fd92 fix(desktop): let inbox title and message author names truncate under narrow panes (#7550)
## Summary

Fixes two instances of the same dead-truncate pattern in the desktop
app, where a flex item's implicit `min-width: auto` prevented `truncate`
from engaging, so long text painted over adjacent controls instead of
ellipsizing:

- **Inbox detail title** (`InboxDetailPane.tsx`): the clickable
context-title button sized to its text instead of shrinking with the
pane, overlapping the header controls (open-in-channel, members, huddle,
more menu). Fixed by adding `max-w-full`.
- **Message author names** (`MessageHeader.tsx` /
`UserProfilePopover.tsx`): the `UserProfilePopover` inline-flex trigger
wrapper refused to shrink below the name's nowrap width, running long
author names under the hover action bar and off the pane edge. Fixed by
adding a `triggerClassName` prop to `UserProfilePopover` and passing
`min-w-0 max-w-full` at the author call site.

Two other suspected instances (project file breadcrumb, drafts pane
title) were stress-tested and already truncate correctly — no change.

### Related issue

N/A — none found.

### Testing

- New Playwright regression tests for both fixes
(`inbox-title-overlap.spec.ts`, `message-author-overlap.spec.ts`,
registered in the smoke project), each proven to discriminate: they fail
with the fix reverted (real measured overlap) and assert the ellipsis
actually engages with non-zero title width, so they can't pass
vacuously.
- Typecheck, lint, and full desktop unit suite green (pre-push hooks);
full desktop e2e smoke suite run earlier: 1402 passed, 3 pre-existing
unrelated failures (each fails identically with the fix reverted).

**Inbox title — before** (long title paints under the header controls):

![Inbox title before: title text overlaps the header control
icons](https://github.com/user-attachments/assets/d5a10f98-114f-4466-8012-468616b82807)

**Inbox title — after** (truncates with ellipsis, controls stay clear):

![Inbox title after: title truncates with an ellipsis before the
controls](https://github.com/user-attachments/assets/80520f7c-1120-4dbc-929d-1ef5eceb874b)

**Author name — before** (long name runs past the header row edge):

![Author name before: name glyphs bleed past the action
bar](https://github.com/user-attachments/assets/be9743ae-ef51-4db0-8c46-a0649e98f0d5)

**Author name — after** (clean cutoff):

![Author name after: name truncates cleanly inside the header
row](https://github.com/user-attachments/assets/24cc0412-ca99-40c4-add6-13380b3ae065)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: cynfria <yescynthia@gmail.com>
Signed-off-by: Tree Trunks <6ba22921d9dc2ad0aa6ecdf63787ddd24726e266d866da31af69f2e4e146ace5@buzz.block.builderlab.xyz>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Tree Trunks <6ba22921d9dc2ad0aa6ecdf63787ddd24726e266d866da31af69f2e4e146ace5@buzz.block.builderlab.xyz>
2026-09-10 10:00:15 -07:00

148 lines
3.8 KiB
TypeScript

import * as React from "react";
import { AgentManagementMarker } from "@/features/agents/ui/OtherSetupAgentMarker";
import { UserProfilePopover } from "@/features/profile/ui/UserProfilePopover";
import { cn } from "@/shared/lib/cn";
type MessageHeaderRowProps = {
children: React.ReactNode;
className?: string;
};
export function MessageHeaderRow({
children,
className,
}: MessageHeaderRowProps) {
return (
<div
className={cn(
"flex min-w-0 flex-wrap items-baseline gap-x-1.5 gap-y-0 leading-message-author",
className,
)}
data-testid="message-header"
>
{children}
</div>
);
}
/**
* Divider between two pieces of message-header metadata.
*
* The header runs several independent facts together on one line, and without a
* divider they read as one phrase: an agent message came out as "managed by You
* 9:53 AM". A middot is what the rest of the app already uses for this
* (`MessageThreadSummaryRow`, project rows, the mention list).
*
* `aria-hidden` because the divider is punctuation for the eye only — the header
* already reads as separate nodes to a screen reader, and `MessageAgentOwner`
* supplies its own "Agent managed by" label.
*
* No horizontal margin: `MessageHeaderRow` is a flex row with `gap-x-1.5`, so
* spacing comes from the container. Adding margin here double-spaces it.
*/
export function MessageMetaSeparator() {
return (
<span aria-hidden="true" className="text-xs text-muted-foreground/40">
·
</span>
);
}
/**
* Renders divider-separated header metadata segments in order, skipping empty
* slots. Each divider is grouped with the segment it precedes so the two wrap
* together — as loose siblings in a flex-wrap row, a divider can end up alone
* at the start of the second line.
*/
export function MessageMetaSegments({
segments,
}: {
segments: ReadonlyArray<{ key: string; node: React.ReactNode }>;
}) {
const present = segments.filter((slot) => Boolean(slot.node));
return (
<>
{present.map(({ key, node }, index) =>
index === 0 ? (
<React.Fragment key={key}>{node}</React.Fragment>
) : (
<span
className="inline-flex min-w-0 items-baseline gap-x-1.5"
key={key}
>
<MessageMetaSeparator />
{node}
</span>
),
)}
</>
);
}
type MessageAuthorTextProps = {
as?: "div" | "h3" | "span";
children: React.ReactNode;
className?: string;
hoverUnderline?: boolean;
};
export function MessageAuthorText({
as: Component = "span",
children,
className,
hoverUnderline = false,
}: MessageAuthorTextProps) {
return (
<Component
className={cn(
"truncate text-message font-semibold leading-message-author tracking-normal",
hoverUnderline && "hover:underline",
className,
)}
data-testid="message-author"
>
{children}
</Component>
);
}
/** Author navigation and provenance always refer to the same exact identity. */
export function MessageAuthorIdentity({
pubkey,
ownerPubkey,
role,
displayName,
children,
}: {
pubkey?: string | null;
ownerPubkey?: string | null;
role?: string;
displayName: string;
children: React.ReactNode;
}) {
return (
<>
{pubkey ? (
<UserProfilePopover
pubkey={pubkey}
role={role}
botIdenticonValue={displayName}
triggerClassName="min-w-0 max-w-full"
>
<button
className="truncate rounded leading-message-author focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
type="button"
>
{children}
</button>
</UserProfilePopover>
) : (
children
)}
<AgentManagementMarker pubkey={pubkey} ownerPubkey={ownerPubkey} />
</>
);
}