diff --git a/frontend/src/components/ChannelTags.tsx b/frontend/src/components/ChannelTags.tsx index 5c70a382..b2a3cb97 100644 --- a/frontend/src/components/ChannelTags.tsx +++ b/frontend/src/components/ChannelTags.tsx @@ -1,4 +1,4 @@ -import { useLayoutEffect, useRef, useState } from "react"; +import { useEffect, useLayoutEffect, useRef, useState } from "react"; import { Check, Plus } from "lucide-react"; import { useTranslation } from "react-i18next"; import { type Tag } from "../lib/api"; @@ -146,7 +146,8 @@ export function ChannelTagsControl({ const rowRef = useRef(null); const measureRef = useRef(null); const [visible, setVisible] = useState(attached.length); - const idsKey = attached.map((tg) => tg.id).join(","); + // Key on id AND name so a RENAME (which changes a chip's width) re-runs the fit, not just add/remove. + const tagsKey = attached.map((tg) => `${tg.id}:${tg.name}`).join("|"); useLayoutEffect(() => { const row = rowRef.current; @@ -158,7 +159,12 @@ export function ChannelTagsControl({ if (!pill) return; const pillW = pill.offsetWidth; const chips = nodes.slice(0, -1); - const GAP = 4; // gap-1 + // Measure the real rendered gap (gap-1 is rem — it scales with the font slider) off the box + // between the last chip and the sample pill, rather than assuming a px constant. + const lastChip = chips[chips.length - 1]; + const gap = lastChip + ? Math.max(0, pill.offsetLeft - (lastChip.offsetLeft + lastChip.offsetWidth)) + : 0; const avail = row.clientWidth; let count = chips.length; for (let i = 0; i < chips.length; i++) { @@ -166,7 +172,7 @@ export function ChannelTagsControl({ if (!chip) break; const right = chip.offsetLeft + chip.offsetWidth; // right edge within the measurer const moreAfter = i < chips.length - 1; - if (right > avail - (moreAfter ? pillW + GAP : 0)) { + if (right > avail - (moreAfter ? pillW + gap : 0)) { count = i; break; } @@ -178,7 +184,7 @@ export function ChannelTagsControl({ const ro = new ResizeObserver(compute); ro.observe(row); return () => ro.disconnect(); - }, [idsKey]); + }, [tagsKey]); const hidden = attached.length - visible; @@ -197,6 +203,11 @@ export function ChannelTagsControl({ mode: "hover", placement: "bottom", }); + // If tags change so nothing is collapsed anymore, the "+N" trigger unmounts — force the reveal-all + // popover's state closed so it can't linger open against a gone reference. + useEffect(() => { + if (hidden === 0 && overflowOpen) setOverflowOpen(false); + }, [hidden, overflowOpen]); const pillClass = "shrink-0 text-[10px] px-1.5 py-0.5 rounded-full bg-card text-muted border border-border";