fix(feed): only tooltip the channel name where it's actually cut off

I set the attribute unconditionally because the one-line row truncates
hard, and never looked at the other four views — where the name fits and
the tooltip just repeated what was already on screen. It now shares the
title's clamped check.
This commit is contained in:
2026-07-17 00:50:40 +02:00
parent 30804dc7dd
commit 638aa5750e
+23 -13
View File
@@ -164,6 +164,9 @@ function RowMetaCells({ video }: { video: Video }) {
? { Icon: VideoIcon, cls: "text-accent", label: t("card.stream") }
: null;
const StatusIcon = status?.Icon;
// 4rem + 1rem + 1rem + two 4px gaps = 104px, which the one-line row's width tally counts on —
// widen a slot and the title column silently pays for it. rem, not px, so the whole cell tracks
// the text-size setting the same way its contents do.
return (
<div className="shrink-0 grid grid-cols-[4rem_1rem_1rem] gap-1 items-center text-sm text-muted">
{/* Right-aligned: a duration is a number, so the digits line up on their own edge (30px for
@@ -356,20 +359,27 @@ function VideoCard({
{video.published_at && <>{" · "}{formatDate(video.published_at, i18n.language)}</>}
</>
);
// Show the full title in a native tooltip only when it's actually cut off — vertically where it
// clamps to 2 lines, horizontally where the one-line row truncates it.
// Show the full text in a native tooltip only when it's actually cut off — vertically where the
// title clamps to 2 lines, horizontally where a fixed column truncates it. Shared by the title
// and the channel name: both truncate in some views and fit in others, and a tooltip that just
// repeats what's already legible is noise.
const titleRef = useRef<HTMLAnchorElement>(null);
const channelRef = useRef<HTMLButtonElement>(null);
const [titleClamped, setTitleClamped] = useState(false);
const [channelClamped, setChannelClamped] = useState(false);
useEffect(() => {
const el = titleRef.current;
if (!el) return;
const check = () =>
setTitleClamped(el.scrollHeight > el.clientHeight + 1 || el.scrollWidth > el.clientWidth + 1);
const cut = (el: HTMLElement) =>
el.scrollHeight > el.clientHeight + 1 || el.scrollWidth > el.clientWidth + 1;
const els: [HTMLElement | null, (v: boolean) => void][] = [
[titleRef.current, setTitleClamped],
[channelRef.current, setChannelClamped],
];
const check = () => els.forEach(([el, set]) => el && set(cut(el)));
check();
const ro = new ResizeObserver(check);
ro.observe(el);
els.forEach(([el]) => el && ro.observe(el));
return () => ro.disconnect();
}, [video.title]);
}, [video.title, video.channel_title]);
const title = (
<a
ref={titleRef}
@@ -388,13 +398,12 @@ function VideoCard({
);
const channelButton = (
<button
ref={channelRef}
onClick={(e) => {
e.stopPropagation();
onOpenChannel?.(video.channel_id, video.channel_title ?? t("card.thisChannel"));
}}
// The name truncates in every layout (hard in the one-line row's fixed column), so it always
// carries the full text — unlike the title, which only earns a tooltip once it's clamped.
title={video.channel_title ?? undefined}
title={channelClamped ? video.channel_title ?? undefined : undefined}
className="text-sm text-muted truncate block w-fit max-w-full text-left hover:text-fg"
>
{video.channel_title}
@@ -418,8 +427,9 @@ function VideoCard({
if (spec.family === "row") {
// A table without being one. Every column is a FIXED width, which is what makes the same datum
// land on the same vertical line in every row — a CSS grid can't do it for us, because
// VirtualFeed renders each row as its own subtree, so there is no shared grid to align to.
// land on the same vertical line in every row. A grid can't align ACROSS rows here — VirtualFeed
// renders each as its own subtree, so there's no shared parent to be the grid — but a grid is
// still right WITHIN a cell (see RowMetaCells): fixed slots, so they line up across rows too.
// Widths are measured, not chosen by eye — against the worst case in the LIBRARY and in BOTH
// languages, since both have caught me out here:
// actions 188 (6 x 28 + 5 x 4) · meta cells 104 (see RowMetaCells) · views 139 ("101.7K