Skip to content

Commit e2e394e

Browse files
authored
fix(ui): give roster rows their avatar-text gap and add presence mock fixtures (#111689)
* fix(ui): add avatar-text gap in roster menu and presence mock fixtures * refactor(ui): keep presence snapshot helper out of the connect auth hunk
1 parent 8c1ea03 commit e2e394e

3 files changed

Lines changed: 53 additions & 2 deletions

File tree

scripts/control-ui-mock-dev.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,13 @@ async function createChatPickerScenario(): Promise<ControlUiMockGatewayScenario>
11281128
"sessions.files.set",
11291129
],
11301130
historyMessages: buildScrollableChatHistory(baseTime),
1131+
// Lights up the footer facepile and who's-online roster; the email-only
1132+
// entry keeps the roster's no-display-name row exercised.
1133+
presenceUsers: [
1134+
{ self: true, id: "presence-riley", name: "Riley", email: "[email protected]" },
1135+
{ id: "presence-colin", name: "Colin", email: "[email protected]" },
1136+
{ id: "presence-patricia", email: "[email protected]" },
1137+
],
11311138
methodResponses: {
11321139
...buildBackgroundTasksMock(baseTime),
11331140
// config.set/config.apply are served statefully by the mock gateway

ui/src/styles/layout.css

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2009,7 +2009,7 @@ html.openclaw-native-macos
20092009
Awesome's checkmark gutter and item metrics so title, avatars, and text
20102010
share one left edge with the rest of the app's menus. */
20112011
.presence-roster-menu__item {
2012-
padding: 5px 8px;
2012+
padding: 6px 8px;
20132013
border-radius: var(--radius-md);
20142014
}
20152015

@@ -2029,10 +2029,14 @@ html.openclaw-native-macos
20292029
}
20302030

20312031
/* Roster avatars grow to two-line row scale. The circle clips via the child
2032-
image/initials instead of the wrapper so the online badge can overflow. */
2032+
image/initials instead of the wrapper so the online badge can overflow.
2033+
The avatar-to-text gap lives here: Web Awesome's icon-slot margin targets
2034+
the slotted openclaw-viewer-avatar host, which is display:contents and
2035+
therefore generates no box for the margin to apply to. */
20332036
.presence-roster-menu__item .viewer-avatar {
20342037
width: 26px;
20352038
height: 26px;
2039+
margin-inline-end: 10px;
20362040
overflow: visible;
20372041
border: none;
20382042
font-size: 10px;

ui/src/test-helpers/control-ui-e2e.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,17 @@ export type ControlUiMockGatewayScenario = {
6666
methodResponses?: Record<string, unknown>;
6767
/** Replayed in-flight run snapshot served by chat.history and chat.startup. */
6868
inFlightRun?: { runId: string; text?: string; plan?: unknown } | null;
69+
/** Online users included in the connect snapshot's presence list. The entry
70+
* flagged `self` adopts the connecting client's instanceId so presence
71+
* surfaces (footer facepile, who's-online roster) resolve "you". */
72+
presenceUsers?: Array<{
73+
self?: boolean;
74+
id: string;
75+
name?: string;
76+
email?: string;
77+
avatarUrl?: string;
78+
watchedSessions?: string[];
79+
}>;
6980
/** Subscription-scoped Gateway events replayed on a fixed browser-side cycle. */
7081
repeatingSessionEvents?: {
7182
events: Array<{ event: "agent" | "session.tool"; payload: unknown }>;
@@ -268,6 +279,7 @@ function normalizeScenario(
268279
historyMessages: scenario.historyMessages ?? [],
269280
methodResponses: scenario.methodResponses ?? {},
270281
inFlightRun: scenario.inFlightRun ?? null,
282+
presenceUsers: scenario.presenceUsers ?? [],
271283
models: scenario.models ?? [{ id: "gpt-5.5", name: "gpt-5.5", provider: "openai" }],
272284
repeatingSessionEvents: scenario.repeatingSessionEvents ?? { events: [] },
273285
sessionInfo: scenario.sessionInfo ?? null,
@@ -583,6 +595,33 @@ function installControlUiMockGateway(input: {
583595
return { found: true, value: matchingCase.response };
584596
}
585597

598+
/** Presence slice of the connect snapshot. The self-flagged entry adopts the
599+
* connecting client's instanceId so presence surfaces resolve "you". */
600+
function presenceSnapshot(connectParams: unknown): { presence?: unknown[] } {
601+
if (scenario.presenceUsers.length === 0) {
602+
return {};
603+
}
604+
const client = isRecord(connectParams) ? connectParams.client : undefined;
605+
const selfInstanceId =
606+
isRecord(client) && typeof client.instanceId === "string"
607+
? client.instanceId
608+
: "e2e-self-instance";
609+
return {
610+
presence: scenario.presenceUsers.map((user, index) => ({
611+
instanceId: user.self ? selfInstanceId : `e2e-presence-${index}`,
612+
mode: "webchat",
613+
reason: "connect",
614+
user: {
615+
id: user.id,
616+
name: user.name ?? null,
617+
email: user.email ?? null,
618+
avatarUrl: user.avatarUrl ?? null,
619+
},
620+
watchedSessions: user.watchedSessions ?? [],
621+
})),
622+
};
623+
}
624+
586625
function recordSessionPatch(params: unknown): void {
587626
if (!isRecord(params) || typeof params.key !== "string") {
588627
return;
@@ -809,6 +848,7 @@ function installControlUiMockGateway(input: {
809848
protocol: protocolVersion,
810849
server: { connId: "control-ui-e2e", version: "e2e" },
811850
snapshot: {
851+
...presenceSnapshot(params),
812852
sessionDefaults: {
813853
defaultAgentId: scenario.defaultAgentId,
814854
mainKey: "main",

0 commit comments

Comments
 (0)