From eaf5401e11b422d8fe2eb6c54c25c9fbeb92f962 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Tue, 3 Nov 2026 22:42:47 +0000 Subject: [PATCH] fix(ui): draw one focus indicator on the composer The message box kept the global 2px focus-visible outline: that rule ties with .home-textarea on specificity and loads later, so the box showed its own ring inside the card ring, a double border. The message box now draws no outline or shadow on focus. The card keeps the one indicator, a 1px inset accent edge plus a faint 2px ring (was 3px), with no transition under prefers-reduced-motion. The chips and buttons keep their own keyboard rings. Home and chat share these rules. Assisted-by: Claude:claude-sonnet-5-5 playwright eslint --- core/http/react-ui/e2e/home-composer.spec.js | 16 ++++++++++++++++ .../react-ui/src/components/home/composer.css | 9 ++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/core/http/react-ui/e2e/home-composer.spec.js b/core/http/react-ui/e2e/home-composer.spec.js index 5ce42edb9a..7f1a0009e0 100644 --- a/core/http/react-ui/e2e/home-composer.spec.js +++ b/core/http/react-ui/e2e/home-composer.spec.js @@ -59,6 +59,22 @@ test.describe('Home composer', () => { await mockHome(page) }) + test('focus draws one indicator, on the card, and the message box draws none', async ({ page }) => { + await server(page) + await ready(page) + const card = page.getByTestId('home-composer') + const box = page.getByTestId('home-input') + const before = (await card.boundingBox()).height + await box.click() + await expect(card).toHaveCSS('box-shadow', /inset/) + await expect(box).toHaveCSS('outline-style', 'none') + await expect(box).toHaveCSS('box-shadow', 'none') + expect((await card.boundingBox()).height).toBe(before) + // the control that takes keyboard focus next keeps its own ring + await page.keyboard.press('Shift+Tab') + await expect(card.locator(':focus-visible')).toHaveCSS('outline-style', 'solid') + }) + test('a file attached on Home arrives with the first message of the new chat', async ({ page }) => { const seen = await server(page) await ready(page) diff --git a/core/http/react-ui/src/components/home/composer.css b/core/http/react-ui/src/components/home/composer.css index 4c10a4f213..617d16edc5 100644 --- a/core/http/react-ui/src/components/home/composer.css +++ b/core/http/react-ui/src/components/home/composer.css @@ -9,7 +9,12 @@ box-shadow: inset 0 0 0 1px var(--dk-control-edge); transition: box-shadow var(--dur-fast) var(--ease-out); } -.home-cmd:focus-within { box-shadow: inset 0 0 0 1px var(--dk-accent-text), 0 0 0 3px color-mix(in srgb, var(--dk-accent) 16%, transparent); } +/* The one focus indicator of the composer, on the card: a 1px accent edge + (inside the box, so nothing moves) and a faint 2px ring. The message box + draws nothing of its own (see .home-textarea); the chips and buttons keep + their own keyboard rings. */ +.home-cmd:focus-within { box-shadow: inset 0 0 0 1px var(--dk-accent-text), 0 0 0 2px color-mix(in srgb, var(--dk-accent) 14%, transparent); } +@media (prefers-reduced-motion: reduce) { .home-cmd { transition: none; } } .home-cmd__top { display: flex; align-items: center; @@ -166,6 +171,8 @@ font-size: var(--fs-body); line-height: var(--lh-body); } +.home-textarea:focus, +.home-textarea:focus-visible { outline: none; box-shadow: none; } .home-textarea::placeholder { color: var(--dk-muted); } .home-file-tags { display: flex; flex-wrap: wrap; gap: 6px; padding-top: var(--sp-3); } .home-cmd__foot { display: flex; align-items: center; gap: var(--sp-1); }