diff --git a/web/src/components/AssistantChat/RichComposerInput.test.tsx b/web/src/components/AssistantChat/RichComposerInput.test.tsx index e877e89db4..68c0c23efc 100644 --- a/web/src/components/AssistantChat/RichComposerInput.test.tsx +++ b/web/src/components/AssistantChat/RichComposerInput.test.tsx @@ -72,6 +72,25 @@ function ProgrammaticEditHarness() { ) } +function MentionHarness() { + const [value, setValue] = useState('') + const ref = useRef(null) + + return ( + <> + + {}} /> + + ) +} + function LineBreakDeletionHarness() { const [value, setValue] = useState('hello') @@ -89,6 +108,22 @@ function LineBreakDeletionHarness() { ) } +describe('RichComposerInput session mention pill', () => { + it('renders the mention pill with working text ellipsis clipping', () => { + render() + + fireEvent.click(screen.getByRole('button', { name: 'Insert mention' })) + + const pill = screen.getByText('@아주 긴 데모 세션 이름 예시 말줄임표 필요') + // Ellipsis lives on the inner label; the pill itself centers it vertically. + expect(pill.className).toContain('truncate') + expect(pill.parentElement?.dataset.composerMention).toBe('session') + expect(pill.parentElement?.className).toContain('inline-flex') + expect(pill.parentElement?.className).toContain('align-baseline') + expect(pill.parentElement?.className).not.toContain('overflow-hidden') + }) +}) + describe('RichComposerInput responsive placeholder', () => { it('calculates a smaller font that fits overflowing text', () => { expect(fitSingleLineFontSize(160, 320)).toBeCloseTo(7.95) diff --git a/web/src/components/AssistantChat/RichComposerInput.tsx b/web/src/components/AssistantChat/RichComposerInput.tsx index cc15aa83d4..bd4e8aee0e 100644 --- a/web/src/components/AssistantChat/RichComposerInput.tsx +++ b/web/src/components/AssistantChat/RichComposerInput.tsx @@ -97,8 +97,14 @@ function createMentionSpan( span.dataset.sessionTitle = title span.dataset.composerMention = 'session' span.className = - 'mx-0.5 inline-flex max-w-[12rem] items-center truncate rounded-md bg-[var(--app-subtle-bg)] px-1.5 py-0.5 align-baseline text-[0.95em] font-medium text-[var(--app-link)]' - span.textContent = `@${title || id.slice(0, 8)}` + 'mx-0.5 inline-flex max-w-[16rem] items-baseline rounded-md bg-[var(--app-subtle-bg)] px-1.5 py-0.5 align-baseline text-[0.95em] font-medium text-[var(--app-link)]' + const label = document.createElement('span') + label.textContent = `@${title || id.slice(0, 8)}` + // Truncation lives on the inner label so the pill's own baseline stays a + // text baseline (an overflow≠visible inline-block would align by its + // bottom edge instead). + label.className = 'min-w-0 truncate' + span.appendChild(label) const tip = resolveTooltip?.(id, title)?.model ?? formatSessionMentionTooltip(null, title, id) span.setAttribute('aria-label', tip.ariaLabel)