mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-29 04:32:57 +00:00
fix(app): truncate agent sender attribution to one line (#8092)
Constrain the sender label to the message width so long agent names do not wrap above received messages. Keep the line limit opt-in on Button and cover compact chat widths with a real-browser regression.
This commit is contained in:
@@ -203,6 +203,47 @@ test("received messages show their sender and open that agent", async ({ page })
|
||||
}
|
||||
});
|
||||
|
||||
test("long sender attribution stays on one line in compact chat", async ({ page }, testInfo) => {
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.addInitScript(() =>
|
||||
localStorage.setItem("@paseo:app-settings", JSON.stringify({ theme: "dark" })),
|
||||
);
|
||||
const recipient = await seedMockAgentWorkspace({
|
||||
repoPrefix: "compact-attribution-",
|
||||
title: "Implementation",
|
||||
});
|
||||
try {
|
||||
const title = "Reviewer checking the compact chat timeline and sender attribution";
|
||||
const sender = await recipient.client.createAgent({
|
||||
provider: "codex",
|
||||
cwd: recipient.cwd,
|
||||
workspaceId: recipient.workspaceId,
|
||||
title,
|
||||
model: "gpt-6.1-sol",
|
||||
modeId: "full-access",
|
||||
});
|
||||
await recipient.client.sendAgentMessage(
|
||||
recipient.agentId,
|
||||
"The sender label stays on one line, even when the agent has a long name.",
|
||||
{ sourceAgentId: sender.id },
|
||||
);
|
||||
await openAgentRoute(page, recipient);
|
||||
await expectComposerVisible(page);
|
||||
const attribution = page.getByRole("button", { name: `Sent by ${title}`, exact: true });
|
||||
await expect(attribution).toBeVisible();
|
||||
await expectSenderTruncatedWithinMessage(attribution, title);
|
||||
await expectSenderAlignedWithBubble(attribution);
|
||||
await attribution.scrollIntoViewIfNeeded();
|
||||
await page.screenshot({ path: testInfo.outputPath("sender-compact-390.png") });
|
||||
await page.setViewportSize({ width: 320, height: 740 });
|
||||
await expectSenderTruncatedWithinMessage(attribution, title);
|
||||
await expectSenderAlignedWithBubble(attribution);
|
||||
await page.screenshot({ path: testInfo.outputPath("sender-compact-320.png") });
|
||||
} finally {
|
||||
await recipient.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
interface MessagePreviewWatch {
|
||||
/** Frames after the watch started on which the fade or "Show more" control was missing. */
|
||||
framesWithoutClip: number[];
|
||||
@@ -316,6 +357,20 @@ test("opening a turn keeps the received message above it clipped on every frame"
|
||||
}
|
||||
});
|
||||
|
||||
async function expectSenderTruncatedWithinMessage(attribution: Locator, title: string) {
|
||||
const label = attribution.getByText(`Sent by ${title}`, { exact: true });
|
||||
await expect(label).toHaveCSS("white-space", "nowrap");
|
||||
await expect(label).toHaveCSS("text-overflow", "ellipsis");
|
||||
const bounds = await label.evaluate((element) => ({
|
||||
textWidth: element.scrollWidth,
|
||||
visibleWidth: element.clientWidth,
|
||||
right: element.getBoundingClientRect().right,
|
||||
viewportWidth: document.documentElement.clientWidth,
|
||||
}));
|
||||
expect(bounds.textWidth).toBeGreaterThan(bounds.visibleWidth);
|
||||
expect(bounds.right).toBeLessThanOrEqual(bounds.viewportWidth);
|
||||
}
|
||||
|
||||
async function expectSenderAlignedWithBubble(attribution: Locator) {
|
||||
const edges = await attribution.evaluate((button) => {
|
||||
const bubble = button.nextElementSibling;
|
||||
|
||||
@@ -168,6 +168,7 @@ export function Button({
|
||||
trailing,
|
||||
style,
|
||||
textStyle,
|
||||
numberOfLines,
|
||||
disabled,
|
||||
loading = false,
|
||||
accessibilityRole,
|
||||
@@ -182,6 +183,7 @@ export function Button({
|
||||
trailing?: ReactNode;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
textStyle?: StyleProp<TextStyle>;
|
||||
numberOfLines?: number;
|
||||
loading?: boolean;
|
||||
}
|
||||
>) {
|
||||
@@ -277,7 +279,11 @@ export function Button({
|
||||
iconSize={buttonIconSize[size]}
|
||||
uniProps={resolveIconMapping()}
|
||||
/>
|
||||
{children != null ? <Text style={resolvedTextStyle}>{children}</Text> : null}
|
||||
{children != null ? (
|
||||
<Text numberOfLines={numberOfLines} style={resolvedTextStyle}>
|
||||
{children}
|
||||
</Text>
|
||||
) : null}
|
||||
{trailing}
|
||||
</Pressable>
|
||||
);
|
||||
|
||||
@@ -80,6 +80,8 @@ export function MessageSender({
|
||||
size="sm"
|
||||
leftIcon={renderProviderIcon}
|
||||
style={styles.sender}
|
||||
textStyle={styles.senderText}
|
||||
numberOfLines={1}
|
||||
disabled={!host}
|
||||
onPress={openSender}
|
||||
>
|
||||
@@ -90,7 +92,8 @@ export function MessageSender({
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
body: { flexShrink: 0 },
|
||||
sender: { paddingRight: 0, borderRightWidth: 0 },
|
||||
sender: { paddingRight: 0, borderRightWidth: 0, maxWidth: "100%" },
|
||||
senderText: { flexShrink: 1 },
|
||||
toggle: { alignSelf: "flex-start", paddingHorizontal: 0, minHeight: 0, borderWidth: 0 },
|
||||
expandedToggle: { marginTop: theme.spacing[2] },
|
||||
fade: { position: "absolute", bottom: 0, left: 0, right: 0, height: theme.spacing[12] },
|
||||
|
||||
Reference in New Issue
Block a user