diff --git a/desktop/src/features/channels/ui/BotActivityBar.tsx b/desktop/src/features/channels/ui/BotActivityBar.tsx index 31e0bfc7a..eadcd0729 100644 --- a/desktop/src/features/channels/ui/BotActivityBar.tsx +++ b/desktop/src/features/channels/ui/BotActivityBar.tsx @@ -291,7 +291,12 @@ function BotActivityAgentPill({ displayName={agent.name} size="xs" /> - + {/* Ticker viewport: 16px tall with a matching 16px line box (leading-4 + overrides the button's leading-none). Inter's ascent+descent ink is + ~1.21em (~14.5px at text-xs) — taller than a leading-none line box — + and BOTH this span and the truncate span clip to their boxes, which + sheared descenders ("g", "y") off the label. */} + setBarHovered(true)} onMouseLeave={() => setBarHovered(false)} @@ -564,9 +579,10 @@ export function BotActivityComposerAction({ strip is scrolled — motion measures positions relative to the scroll offset instead of jumping. The negative-margin/padding pair gives focus rings and pill shadows room inside the clip box - without changing the row's height. */} + without changing the row's height. px-5 restores the row gutter + inside the full-bleed viewport (see the wrapper above). */} {/* Edge fades: obvious "more pills off view" affordance. Rendered only for a side that actually has clipped content, so they never dim a - fully visible strip. */} + fully visible strip. They sit at the full-bleed viewport's edges — + the container's visual edges — and w-12 spans the 20px gutter plus + a soft zone over the pills scrolling under it. */} {fades.start ? (
) : null} {fades.end ? (
) : null} diff --git a/desktop/tests/e2e/channels.spec.ts b/desktop/tests/e2e/channels.spec.ts index b57fd0238..4716cd49f 100644 --- a/desktop/tests/e2e/channels.spec.ts +++ b/desktop/tests/e2e/channels.spec.ts @@ -2482,12 +2482,39 @@ test("narrow strip scrolls horizontally with edge fades instead of compressing p 0, ); + // The fade sits at the CONTAINER's visual edge, not at the row's padded + // content box: the full-bleed viewport cancels the row's px-5 gutter, so + // pills scroll all the way under a gradient pinned to the row's border + // box instead of getting cut mid-row with a dead gutter after the fade. + const rowBox = await page + .getByTestId("channel-composer-activity-row") + .boundingBox(); + const fadeEndBox = await page + .getByTestId("bot-activity-strip-fade-end") + .boundingBox(); + expect(fadeEndBox).not.toBeNull(); + expect(rowBox).not.toBeNull(); + if (fadeEndBox && rowBox) { + expect( + Math.abs(fadeEndBox.x + fadeEndBox.width - (rowBox.x + rowBox.width)), + ).toBeLessThanOrEqual(1); + } + // Scrolling to the far end swaps the fades to the leading side. await scroller.evaluate((el) => { el.scrollLeft = el.scrollWidth; }); await expect(page.getByTestId("bot-activity-strip-fade-start")).toBeVisible(); await expect(page.getByTestId("bot-activity-strip-fade-end")).toHaveCount(0); + + // Same pinning on the leading edge once scrolled. + const fadeStartBox = await page + .getByTestId("bot-activity-strip-fade-start") + .boundingBox(); + expect(fadeStartBox).not.toBeNull(); + if (fadeStartBox && rowBox) { + expect(Math.abs(fadeStartBox.x - rowBox.x)).toBeLessThanOrEqual(1); + } }); test("lone pill shrinks to fit a narrow container without scroll fades", async ({