From bd83da552da45d1d250057bb50e44fb19381c4d6 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Fri, 4 Sep 2026 00:07:08 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=93=20fix:=20Match=20Pasted=20Text=20F?= =?UTF-8?q?ocus=20Feedback=20(#15588)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review follow-up. The label's colour shift answered the pointer only. The focus ring already announces focus, so this is parity rather than a missing indicator — but two input modes getting different feedback for the same state was a difference with nothing behind it. The hover-gate guard keeps asserting class tokens, brittle as that is. jsdom loads no stylesheet, so a hover gate has no effect on the DOM under test and a presence check cannot see one: the swap this replaced kept both labels mounted, which is why the spec it replaced asserted both were in the document at once. Recorded the reasoning where the next reader will meet it. --- .../Chat/Input/Files/FileContainer.tsx | 6 ++++-- .../Files/__tests__/FileContainer.spec.tsx | 21 +++++++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/client/src/components/Chat/Input/Files/FileContainer.tsx b/client/src/components/Chat/Input/Files/FileContainer.tsx index e914be08d2..3f47b0b545 100644 --- a/client/src/components/Chat/Input/Files/FileContainer.tsx +++ b/client/src/components/Chat/Input/Files/FileContainer.tsx @@ -95,8 +95,10 @@ const FileContainer = ({ * subtitle it would otherwise read as a description rather than a control. The * hit area is the label and nothing more: `inline-block` keeps it off the rest * of the row, and `leading-4` keeps it inside its 20px line so the chip's own - * center still opens the editor. */ - 'pointer-events-auto relative z-10 inline-block max-w-full truncate rounded text-left align-middle leading-4 text-text-secondary underline underline-offset-2 hover:text-text-primary', + * center still opens the editor. The colour shift answers focus as well as + * hover: the ring already announces focus, but leaving the two input modes + * with different feedback is a difference with no reason behind it. */ + 'pointer-events-auto relative z-10 inline-block max-w-full truncate rounded text-left align-middle leading-4 text-text-secondary underline underline-offset-2 hover:text-text-primary focus-visible:text-text-primary', focusRing, )} > diff --git a/client/src/components/Chat/Input/Files/__tests__/FileContainer.spec.tsx b/client/src/components/Chat/Input/Files/__tests__/FileContainer.spec.tsx index 6c44251613..e094bb2742 100644 --- a/client/src/components/Chat/Input/Files/__tests__/FileContainer.spec.tsx +++ b/client/src/components/Chat/Input/Files/__tests__/FileContainer.spec.tsx @@ -77,12 +77,17 @@ describe('FileContainer subtitle action', () => { /** The affordance used to live behind a `group-hover` swap, which left the chip reading as * an inert "Plain Text" until pointed at — so nobody found it, and touch had no pointer to - * find it with. Nothing about the label may be conditional on hover or focus again. */ + * find it with. Nothing about the label may be conditional on hover or focus again. + * + * Asserted on class tokens deliberately, brittle as that is. jsdom loads no stylesheet, so + * a hover gate has no effect on the DOM here and a presence check cannot see one: the swap + * this replaced kept BOTH labels mounted, which is why the spec it replaced asserted they + * were both in the document at once. Only the tokens distinguish the two markups. */ const control = screen.getByRole('button', { name: 'Move back into message' }); expect(control.className).not.toContain('group-hover'); expect(control.className).not.toContain('group-focus-within'); expect(control.className).not.toContain('hover:none'); - expect(control.textContent).toBe('Move back into message'); + expect(control).toHaveTextContent('Move back into message'); }); it('names the subtitle control from its own visible label', () => { @@ -164,6 +169,18 @@ describe('FileContainer subtitle action', () => { expect(control.className).not.toContain('hover:underline'); }); + it('gives keyboard focus the same feedback the pointer gets', () => { + render( + , + ); + + /** The focus ring already announces focus, so this is parity rather than a missing + * indicator: whatever the colour shift signals to a pointer, it signals to a keyboard. */ + const control = screen.getByRole('button', { name: 'Move back into message' }); + expect(control.className).toContain('hover:text-text-primary'); + expect(control.className).toContain('focus-visible:text-text-primary'); + }); + it('leaves the plain subtitle alone when no action is offered', () => { render();