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
This commit is contained in:
Peter Steinberger
2026-07-19 23:38:40 -07:00
committed by GitHub
parent 8c1ea03da0
commit e2e394e1ab
3 changed files with 53 additions and 2 deletions
+7
View File
@@ -1128,6 +1128,13 @@ async function createChatPickerScenario(): Promise<ControlUiMockGatewayScenario>
"sessions.files.set",
],
historyMessages: buildScrollableChatHistory(baseTime),
// Lights up the footer facepile and who's-online roster; the email-only
// entry keeps the roster's no-display-name row exercised.
presenceUsers: [
{ self: true, id: "presence-riley", name: "Riley", email: "riley@example.com" },
{ id: "presence-colin", name: "Colin", email: "colin@example.com" },
{ id: "presence-patricia", email: "patricia.erichsen@example.com" },
],
methodResponses: {
...buildBackgroundTasksMock(baseTime),
// config.set/config.apply are served statefully by the mock gateway
+6 -2
View File
@@ -2009,7 +2009,7 @@ html.openclaw-native-macos
Awesome's checkmark gutter and item metrics so title, avatars, and text
share one left edge with the rest of the app's menus. */
.presence-roster-menu__item {
padding: 5px 8px;
padding: 6px 8px;
border-radius: var(--radius-md);
}
@@ -2029,10 +2029,14 @@ html.openclaw-native-macos
}
/* Roster avatars grow to two-line row scale. The circle clips via the child
image/initials instead of the wrapper so the online badge can overflow. */
image/initials instead of the wrapper so the online badge can overflow.
The avatar-to-text gap lives here: Web Awesome's icon-slot margin targets
the slotted openclaw-viewer-avatar host, which is display:contents and
therefore generates no box for the margin to apply to. */
.presence-roster-menu__item .viewer-avatar {
width: 26px;
height: 26px;
margin-inline-end: 10px;
overflow: visible;
border: none;
font-size: 10px;
+40
View File
@@ -66,6 +66,17 @@ export type ControlUiMockGatewayScenario = {
methodResponses?: Record<string, unknown>;
/** Replayed in-flight run snapshot served by chat.history and chat.startup. */
inFlightRun?: { runId: string; text?: string; plan?: unknown } | null;
/** Online users included in the connect snapshot's presence list. The entry
* flagged `self` adopts the connecting client's instanceId so presence
* surfaces (footer facepile, who's-online roster) resolve "you". */
presenceUsers?: Array<{
self?: boolean;
id: string;
name?: string;
email?: string;
avatarUrl?: string;
watchedSessions?: string[];
}>;
/** Subscription-scoped Gateway events replayed on a fixed browser-side cycle. */
repeatingSessionEvents?: {
events: Array<{ event: "agent" | "session.tool"; payload: unknown }>;
@@ -268,6 +279,7 @@ function normalizeScenario(
historyMessages: scenario.historyMessages ?? [],
methodResponses: scenario.methodResponses ?? {},
inFlightRun: scenario.inFlightRun ?? null,
presenceUsers: scenario.presenceUsers ?? [],
models: scenario.models ?? [{ id: "gpt-5.5", name: "gpt-5.5", provider: "openai" }],
repeatingSessionEvents: scenario.repeatingSessionEvents ?? { events: [] },
sessionInfo: scenario.sessionInfo ?? null,
@@ -583,6 +595,33 @@ function installControlUiMockGateway(input: {
return { found: true, value: matchingCase.response };
}
/** Presence slice of the connect snapshot. The self-flagged entry adopts the
* connecting client's instanceId so presence surfaces resolve "you". */
function presenceSnapshot(connectParams: unknown): { presence?: unknown[] } {
if (scenario.presenceUsers.length === 0) {
return {};
}
const client = isRecord(connectParams) ? connectParams.client : undefined;
const selfInstanceId =
isRecord(client) && typeof client.instanceId === "string"
? client.instanceId
: "e2e-self-instance";
return {
presence: scenario.presenceUsers.map((user, index) => ({
instanceId: user.self ? selfInstanceId : `e2e-presence-${index}`,
mode: "webchat",
reason: "connect",
user: {
id: user.id,
name: user.name ?? null,
email: user.email ?? null,
avatarUrl: user.avatarUrl ?? null,
},
watchedSessions: user.watchedSessions ?? [],
})),
};
}
function recordSessionPatch(params: unknown): void {
if (!isRecord(params) || typeof params.key !== "string") {
return;
@@ -809,6 +848,7 @@ function installControlUiMockGateway(input: {
protocol: protocolVersion,
server: { connId: "control-ui-e2e", version: "e2e" },
snapshot: {
...presenceSnapshot(params),
sessionDefaults: {
defaultAgentId: scenario.defaultAgentId,
mainKey: "main",