fix(composer): lone typing group uses the full row width instead of truncating at the strip cap

- BotActivityBar.tsx: apply the typing slot's max-w-64 cap only when the
  group shares the strip with working-agent pills. The cap exists so a
  long "X, Y, and N more are typing" label can't inflate the strip's
  scroll extent alongside pills, but it was unconditional — as the
  strip's LONE item a two-name label ("Alpha Debug and Beta Debug are
  typing...") got cut off at 256px even with the row mostly empty. The
  lone item already shrinks with the container (min-w-0 through the
  slot chain), so it now consumes the row's free width and only
  ellipsizes when the container is genuinely narrow.
- channels.spec.ts: add "lone typing group uses the full row width"
  e2e regression — three typers push the label past the old cap, then
  asserts no truncation at full width (scrollWidth <= clientWidth) and
  graceful ellipsizing without scroll fades at a 200px row. Verified
  the test fails against the old unconditional cap.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
This commit is contained in:
Taylor Ho
2026-08-05 16:42:02 -07:00
committed by Carl
parent e5b77b9b15
commit fb2a99598c
2 changed files with 56 additions and 4 deletions
@@ -775,10 +775,16 @@ export function BotActivityComposerAction({
{() => (
<div
className={cn(
// Cap like the pills (which use max-w-50 each) so a
// long "X, Y, and N more are typing" label truncates
// instead of inflating the scroll extent.
"flex h-7 min-w-0 max-w-64 items-center",
"flex h-7 min-w-0 items-center",
// Only when sharing the strip with pills does the
// group need a width cap (like the pills' max-w-50
// each) so a long "X, Y, and N more are typing" label
// truncates instead of inflating the scroll extent.
// As the strip's lone item it shrinks with the
// container instead (min-w-0 through the slot chain),
// so a cap would only cut the label off in a row that
// has room to spare.
orderedAgents.length > 0 && "max-w-64",
// Composer-edge alignment when the typing group leads
// the strip (no pills): 0.75rem/1rem composer padding
// + 1px border. Mirrors the standalone row's old
+46
View File
@@ -3109,6 +3109,52 @@ test("typing indicator shows avatars and maintains stable name order", async ({
).toContainText("alice and bob are typing");
});
test("lone typing group uses the full row width instead of truncating at the strip cap", async ({
page,
}) => {
await page.goto("/");
await page.getByTestId("channel-random").click();
await expect(page.getByTestId("chat-title")).toHaveText("random");
await waitForMockLiveSubscription(page, "random", KIND_TYPING_INDICATOR);
// Three typers push the label past the strip's shared-slot cap (max-w-64,
// 256px): with no pills the typing group is the strip's LONE item and must
// consume the row's free width instead of cutting the label off mid-name.
for (const identity of [
TEST_IDENTITIES.alice,
TEST_IDENTITIES.bob,
TEST_IDENTITIES.charlie,
]) {
await page.evaluate((pubkey) => {
window.__BUZZ_E2E_EMIT_MOCK_TYPING__?.({
channelName: "random",
pubkey,
});
}, identity.pubkey);
}
const label = page.getByTestId("message-typing-indicator-label");
await expect(label).toContainText("alice, bob, and charlie are typing");
// No truncation with room to spare: the label's content fits its box.
await expect
.poll(() => label.evaluate((el) => el.scrollWidth - el.clientWidth))
.toBeLessThanOrEqual(0);
// Genuinely narrow, the lone group still shrinks with the container and
// ellipsizes (rather than overflowing into scroll under an edge fade).
await page
.getByTestId("channel-composer-activity-row")
.evaluate((element) => {
element.style.width = "200px";
});
await expect
.poll(() => label.evaluate((el) => el.scrollWidth - el.clientWidth))
.toBeGreaterThan(0);
await expect(page.getByTestId("bot-activity-strip-fade-end")).toHaveCount(0);
});
test("sidebar shows unread indicator for newly active channels", async ({
page,
}) => {