fix(ui): contain long sharing member labels (#123017)

This commit is contained in:
Vyctor H. Brzezowski
2026-08-13 23:19:26 -03:00
committed by GitHub
parent c489fea233
commit b0583e2643
4 changed files with 109 additions and 10 deletions
+85 -7
View File
@@ -481,11 +481,18 @@ suite.define(() => {
sessions.count = 1;
sessions.creators = [{ id: "profile-ada", label: "Ada" }];
sessions.sessions = [activeSession];
const humanIdentities = Array.from({ length: 30 }, (_, index) => ({
type: "human" as const,
id: `profile-member-${index}`,
label: `Member ${index + 1}`,
}));
const longMemberLabel =
"Alexandria Montgomery-Santiago from the International Collaboration Working Group";
const longMemberId = `profile:${"member-without-a-display-name-".repeat(6)}`;
const humanIdentities = [
{ type: "human" as const, id: "profile-long-name", label: longMemberLabel },
{ type: "human" as const, id: longMemberId },
...Array.from({ length: 28 }, (_, index) => ({
type: "human" as const,
id: `profile-member-${index}`,
label: `Member ${index + 1}`,
})),
];
const channelIdentities = [
{ type: "human" as const, id: "channel:chn_design", label: "Design" },
{ type: "human" as const, id: "discord:channel:operations", label: "Operations" },
@@ -502,12 +509,41 @@ suite.define(() => {
"session.visibility.set",
],
operatorScopes: ["operator.read", "operator.write"],
historyMessages: [{ role: "assistant", content: [{ type: "text", text: "Ready." }] }],
historyMessages: [
{
role: "user",
content: [
{
type: "text",
text: "Share the launch review with the design and operations groups, then summarize the open decisions.",
},
],
},
{
role: "assistant",
content: [
{
type: "text",
text: "I prepared the rollout summary, linked the review notes, and kept the workspace visible to collaborators.",
},
],
},
{
role: "user",
content: [
{
type: "text",
text: "Add the remaining members and confirm the sharing policy before the handoff.",
},
],
},
{ role: "assistant", content: [{ type: "text", text: "Ready." }] },
],
methodResponses: {
"sessions.list": sessions,
"session.members.list": {
sessionKey: "agent:main:ada",
members: [],
members: [{ identityId: longMemberId, addedBy: "profile-ada", addedAt: 1 }],
identities: [...humanIdentities, ...channelIdentities],
role: "owner",
allowedVisibilities: ["shared", "read-only", "suggest", "draft"],
@@ -526,9 +562,29 @@ suite.define(() => {
const animations = [...element.getAnimations(), ...(menu?.getAnimations() ?? [])];
await Promise.all(animations.map((animation) => animation.finished.catch(() => {})));
});
const longNameItem = dropdown.locator('wa-dropdown-item[value="member:profile-long-name"]');
const longIdItem = dropdown.locator(`wa-dropdown-item[value="member:${longMemberId}"]`);
await longIdItem.scrollIntoViewIfNeeded();
const selectedIndicator = longIdItem.locator('[slot="details"]');
await expectBrowser(selectedIndicator).toBeVisible();
const selectedIndicatorContained = await longIdItem.evaluate((item) => {
const indicator = item.querySelector<HTMLElement>('[slot="details"]');
const itemRect = item.getBoundingClientRect();
const indicatorRect = indicator?.getBoundingClientRect();
return Boolean(
indicatorRect &&
indicatorRect.left >= itemRect.left &&
indicatorRect.right <= itemRect.right,
);
});
expect(selectedIndicatorContained).toBe(true);
const beforeScroll = await dropdown.evaluate((element) => {
const menu = element.shadowRoot?.querySelector<HTMLElement>('[part="menu"]');
if (menu) {
menu.scrollTop = 0;
}
const visibilityTitle = element.querySelector<HTMLElement>(
".chat-pane__sharing-visibility-title",
);
@@ -537,6 +593,10 @@ suite.define(() => {
];
const membersTitle = element.querySelector<HTMLElement>(".chat-pane__sharing-members-title");
const firstMember = element.querySelector<HTMLElement>(".chat-pane__sharing-member");
const longLabels = [
...element.querySelectorAll<HTMLElement>(".chat-pane__sharing-member-label"),
].slice(0, 2);
const menuRect = menu?.getBoundingClientRect();
const previousVisibilityRect = visibilityItems.at(-2)?.getBoundingClientRect();
const lastVisibilityRect = visibilityItems.at(-1)?.getBoundingClientRect();
const membersTitleRect = membersTitle?.getBoundingClientRect();
@@ -545,6 +605,13 @@ suite.define(() => {
menuTop: menu?.getBoundingClientRect().top ?? 0,
scrollHeight: menu?.scrollHeight ?? 0,
clientHeight: menu?.clientHeight ?? 0,
scrollWidth: menu?.scrollWidth ?? 0,
clientWidth: menu?.clientWidth ?? 0,
longLabelsContained: longLabels.every((label) => {
const rect = label.getBoundingClientRect();
return menuRect ? rect.left >= menuRect.left && rect.right <= menuRect.right : false;
}),
longLabelsOverflow: longLabels.every((label) => label.scrollWidth > label.clientWidth),
firstMemberTop: firstMember?.getBoundingClientRect().top ?? 0,
groupGap:
membersTitleRect && lastVisibilityRect
@@ -594,6 +661,9 @@ suite.define(() => {
expect(beforeScroll.menuHeight).toBeLessThanOrEqual(421);
expect(beforeScroll.scrollHeight).toBeGreaterThan(beforeScroll.clientHeight);
expect(beforeScroll.scrollWidth).toBe(beforeScroll.clientWidth);
expect(beforeScroll.longLabelsContained).toBe(true);
expect(beforeScroll.longLabelsOverflow).toBe(true);
expect(beforeScroll.membersTitleInset).toBe(beforeScroll.firstMemberInset);
expect(beforeScroll.groupGap).toBeGreaterThanOrEqual(8);
expect(beforeScroll.groupGap).toBeGreaterThan(beforeScroll.rowGap + 6);
@@ -608,6 +678,14 @@ suite.define(() => {
dropdown.locator(".chat-pane__sharing-member openclaw-session-owner-chip"),
).toHaveCount(30);
await expectBrowser(dropdown.locator(".chat-pane__sharing-channel-icon")).toHaveCount(2);
expect(
await longNameItem.locator(".chat-pane__sharing-member-label").getAttribute("title"),
).toBe(longMemberLabel);
expect(await longIdItem.locator(".chat-pane__sharing-member-label").getAttribute("title")).toBe(
longMemberId,
);
await expectBrowser(selectedIndicator).toHaveCount(1);
expect(await selectedIndicator.getAttribute("aria-label")).not.toBeNull();
});
it("clears a selected draft mode when sharing policy becomes unavailable", async () => {
@@ -191,8 +191,16 @@ describe("chat session sharing menu", () => {
sessionKey: "agent:main:main",
members: [{ identityId: "alice", addedBy: "owner", addedAt: 1 }],
identities: [
{ type: "human", id: "alice", label: "Alice" },
{ type: "human", id: "bob", label: "Bob" },
{
type: "human",
id: "alice",
label: "Alice with a very long selected member display name",
},
{
type: "human",
id: "bob",
label: "Bob with a very long available member display name",
},
],
role: "owner",
allowedVisibilities: ["shared", "read-only"],
@@ -217,6 +225,15 @@ describe("chat session sharing menu", () => {
expect(
root.querySelector('wa-dropdown-item[value="member:bob"]')?.hasAttribute("disabled"),
).toBe(true);
for (const identityId of ["alice", "bob"]) {
const item = root.querySelector<HTMLElement>(
`wa-dropdown-item[value="member:${identityId}"]`,
);
expect(item?.title).toBe("Requires write");
expect(item?.querySelector(".chat-pane__sharing-member-label")?.hasAttribute("title")).toBe(
false,
);
}
dropdown?.dispatchEvent(new CustomEvent("wa-show"));
dropdown?.dispatchEvent(
@@ -183,12 +183,15 @@ export function renderChatSessionSharing(props: ChatSessionSharingProps) {
? renderSessionOwnerChip(identity, "header")
: icons.bot}
</span>
<span class="chat-pane__sharing-member-label"
<span
class="chat-pane__sharing-member-label"
title=${disabledReason ? nothing : (identity.label ?? identity.id)}
>${identity.label ?? identity.id}</span
>
${members.has(identity.id)
? html`<span
slot="details"
class="session-menu__check"
aria-label=${t("chat.sessionSharing.selected")}
>${icons.check}</span
>`
+1
View File
@@ -641,6 +641,7 @@ openclaw-chat-pane {
}
.chat-pane__sharing-member-label {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;