From ab2bd4faa87ed68aba00615317f8ea4587795289 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 18 Aug 2026 11:41:06 -0700 Subject: [PATCH] feat(ui): consolidate activity filters into toolbar with people popover (#125917) * test(ui): seed mock activity sessions across owners * feat(ui): consolidate activity filters into toolbar Also restores the shared .input styling contract for standalone form controls. * test(ui): update activity feed e2e for toolbar people popover --- scripts/control-ui-mock-dev.ts | 40 ++++ .../activity-session-feed.capture.e2e.test.ts | 12 +- ui/src/i18n/locales/en.ts | 5 + ui/src/pages/activity/activity-page.ts | 4 +- .../activity/session-activity-view.test.ts | 112 +++++++++ .../pages/activity/session-activity-view.ts | 226 +++++++++++++++--- .../pages/activity/session-activity.test.ts | 28 +++ ui/src/pages/activity/session-activity.ts | 11 +- ui/src/styles/activity.css | 221 ++++++++++++----- ui/src/styles/components.css | 13 +- 10 files changed, 563 insertions(+), 109 deletions(-) create mode 100644 ui/src/pages/activity/session-activity-view.test.ts diff --git a/scripts/control-ui-mock-dev.ts b/scripts/control-ui-mock-dev.ts index 9b432978e3f0..bf21179b9ceb 100644 --- a/scripts/control-ui-mock-dev.ts +++ b/scripts/control-ui-mock-dev.ts @@ -294,6 +294,44 @@ function buildSearchSessionListCases( return searchTerms.flatMap((search) => buildSessionListCases(sessions, { search })); } +function buildActivitySessionRows(baseTime: number) { + const owners = { + molty: { type: "agent", id: "profile-molty", label: "Molty" }, + riley: { type: "human", id: "presence-riley", label: "Riley" }, + colin: { type: "human", id: "presence-colin", label: "Colin" }, + patricia: { + type: "human", + id: "presence-patricia", + label: "patricia.erichsen@example.com", + }, + unresolved: { type: "human", id: "147591189530201337" }, + } as const; + const hour = 60 * 60 * 1000; + const day = 24 * hour; + const fixtures = [ + ["release-check", "Release readiness check", owners.riley, 5 * 60_000], + ["api-notes", "Gateway API notes", owners.molty, 20 * 60_000], + ["design-review", "Activity feed design review", owners.colin, hour], + ["archive-audit", "Archive retention audit", owners.unresolved, 3 * hour], + ["support-handoff", "Support handoff", owners.patricia, 6 * hour], + ["mobile-smoke", "Mobile layout smoke test", owners.riley, 18 * hour], + ["provider-matrix", "Provider matrix cleanup", owners.molty, 30 * hour], + ["docs-pass", "Operator docs pass", owners.colin, 2 * day], + ["queue-review", "Queue behavior review", owners.patricia, 2.5 * day], + ["identity-trace", "Identity trace", owners.unresolved, 3 * day], + ["channel-followup", "Channel delivery follow-up", owners.riley, 4 * day], + ["tooling-refresh", "Tooling refresh", owners.molty, 5 * day], + ["fixture-polish", "Mock fixture polish", owners.colin, 6 * day], + ["weekly-summary", "Weekly activity summary", owners.patricia, 6.5 * day], + ] as const; + return fixtures.map(([key, label, owner, age]) => + sessionRow(`agent:activity:${key}`, label, baseTime - age, { + createdActor: owner, + owner: { actor: owner }, + }), + ); +} + function usageCostTotals(totalTokens: number, totalCost = 0) { return { input: Math.round(totalTokens * 0.2), @@ -1418,7 +1456,9 @@ async function createChatPickerScenario( ] : []; const workboardMocks = buildWorkboardMocks(baseTime); + const activitySessions = buildActivitySessionRows(Date.now()); const sessions = [ + ...activitySessions, ...(fixture === "workboard" ? [ sessionRow(workboardMocks.sessionKey, "Product operations dashboard", baseTime, { diff --git a/ui/src/e2e/activity-session-feed.capture.e2e.test.ts b/ui/src/e2e/activity-session-feed.capture.e2e.test.ts index 0a6b4e1e5e61..6979c679f598 100644 --- a/ui/src/e2e/activity-session-feed.capture.e2e.test.ts +++ b/ui/src/e2e/activity-session-feed.capture.e2e.test.ts @@ -136,7 +136,17 @@ suite.define(() => { app.runtime?.context.navigate("activity"); }); await waitForControlUiRoute(page, { pathname: "/activity", routeId: "activity" }); - await expect.poll(() => page.locator(".activity-feed__person").count()).toBe(3); + await page.locator(".activity-feed__people-trigger").click(); + await expect + .poll(() => + page + .locator( + '.activity-feed__people-row[data-activity-person]:not([data-activity-person=""])', + ) + .count(), + ) + .toBe(3); + await page.keyboard.press("Escape"); await expect .poll(() => page.locator(".activity-feed__sessions > .activity-feed__session").count()) .toBe(4); diff --git a/ui/src/i18n/locales/en.ts b/ui/src/i18n/locales/en.ts index bc8b1fb83458..49261e2d5adb 100644 --- a/ui/src/i18n/locales/en.ts +++ b/ui/src/i18n/locales/en.ts @@ -3363,6 +3363,11 @@ export const en: TranslationMap = { timeAll: "All time", people: "People", allPeople: "All", + everyone: "Everyone", + peopleButtonLabel: "Filter sessions by person", + lastActive: "· {time}", + unresolvedIdentities: "Unresolved identities", + clearPersonFilter: "Clear person filter", sessions: "Sessions", showing: "Showing {shown} of {total}", today: "Today", diff --git a/ui/src/pages/activity/activity-page.ts b/ui/src/pages/activity/activity-page.ts index 94e75a8b88a1..3fc3afaa1ec9 100644 --- a/ui/src/pages/activity/activity-page.ts +++ b/ui/src/pages/activity/activity-page.ts @@ -21,7 +21,7 @@ import { renderSettingsWorkspace } from "../../components/settings-workspace.ts" import { t } from "../../i18n/index.ts"; import { isMissingOperatorReadScopeError } from "../../lib/gateway-errors.ts"; import { canCallGatewayMethod, isGatewayMethodAdvertised } from "../../lib/gateway-methods.ts"; -import type { PresenceViewer } from "../../lib/presence-users.ts"; +import { projectPresencePayload, type PresenceViewer } from "../../lib/presence-users.ts"; import { resolveSessionKey } from "../../lib/sessions/index.ts"; import { uiSessionEventMatches } from "../../lib/sessions/session-key.ts"; import { OpenClawLightDomElement } from "../../lit/openclaw-element.ts"; @@ -478,6 +478,7 @@ class ActivityPage extends OpenClawLightDomElement { this.routeData.mode === "sessions" ? this.routeData.filters : ({ personId: null, query: "", time: "7d" } satisfies SessionActivityFilters); + const presenceViewers = projectPresencePayload(this.presencePayload).users; const currentIdentity = filters.personId ? resolveActivityIdentity(filters.personId, this.presencePayload, sessionRows) : null; @@ -517,6 +518,7 @@ class ActivityPage extends OpenClawLightDomElement { ? renderSessionActivityView({ context: this.context, filters, + presenceViewers, retainedIdentity, rows: sessionRows, onFiltersChange: (next) => diff --git a/ui/src/pages/activity/session-activity-view.test.ts b/ui/src/pages/activity/session-activity-view.test.ts new file mode 100644 index 000000000000..a10cbc2ebc92 --- /dev/null +++ b/ui/src/pages/activity/session-activity-view.test.ts @@ -0,0 +1,112 @@ +/* @vitest-environment jsdom */ + +import { render } from "lit"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { GatewaySessionRow } from "../../api/types.ts"; +import type { ApplicationContext } from "../../app/context.ts"; +import type { PresenceViewer } from "../../lib/presence-users.ts"; +import { renderSessionActivityView } from "./session-activity-view.ts"; + +function row(key: string, owner: { id: string; label?: string }, updatedAt: number) { + const actor = { type: "human" as const, ...owner }; + return { + key, + kind: "direct", + displayName: key, + updatedAt, + createdActor: actor, + owner: { actor }, + } satisfies GatewaySessionRow; +} + +function props(overrides: Partial[0]> = {}) { + return { + context: { + basePath: "", + navigate: vi.fn(), + gateway: { snapshot: { hello: null } }, + agents: { state: { agentsList: { defaultId: "main", mainKey: "main" } } }, + agentSelection: { state: { selectedId: "main" } }, + sessions: { state: { result: { sessions: [] } } }, + } as unknown as ApplicationContext, + filters: { personId: null, query: "", time: "7d" as const }, + presenceViewers: [] as PresenceViewer[], + retainedIdentity: null, + rows: [] as GatewaySessionRow[], + onFiltersChange: vi.fn(), + ...overrides, + }; +} + +describe("session activity people filter", () => { + beforeEach(() => { + document.body.innerHTML = ""; + }); + + it("separates raw fallback identities and maps presence dots by exact viewer id", () => { + const now = Date.now(); + const container = document.createElement("div"); + document.body.append(container); + + render( + renderSessionActivityView( + props({ + rows: [ + row("Online session", { id: "online", label: "Online person" }, now), + row("Offline session", { id: "offline", label: "Offline person" }, now - 1_000), + row("Unknown session", { id: "147591189530201337" }, now - 2_000), + row("Explicit label session", { id: "explicit-id", label: "explicit-id" }, now - 3_000), + ], + presenceViewers: [ + { + id: "online", + name: "Online person", + watchedSessions: [], + entries: [{ instanceId: "online-device", user: { id: "online" }, ts: now }], + }, + ], + }), + ), + container, + ); + + expect( + container.querySelector('[data-activity-person="online"] .activity-feed__presence-dot'), + ).not.toBeNull(); + expect( + container.querySelector('[data-activity-person="offline"] .activity-feed__presence-dot'), + ).toBeNull(); + expect( + container.querySelector('[data-activity-person="offline"] .activity-feed__last-active'), + ).not.toBeNull(); + const unresolved = container.querySelector("[data-activity-unresolved]"); + expect(unresolved?.textContent).toContain("14759118…"); + expect(unresolved?.textContent).not.toContain("147591189530201337"); + expect(unresolved?.querySelector('[data-activity-person="explicit-id"]')).toBeNull(); + }); + + it("selecting Everyone clears the person while preserving the other filters", () => { + const onFiltersChange = vi.fn(); + const container = document.createElement("div"); + document.body.append(container); + render( + renderSessionActivityView( + props({ + filters: { personId: "online", query: "release", time: "30d" }, + retainedIdentity: { id: "online", name: "Online person", watchedSessions: [] }, + rows: [row("Release session", { id: "online", label: "Online person" }, Date.now())], + onFiltersChange, + }), + ), + container, + ); + + container.querySelector('[data-activity-person=""]')?.click(); + + expect(onFiltersChange).toHaveBeenCalledWith({ + personId: null, + query: "release", + time: "30d", + }); + }); +}); diff --git a/ui/src/pages/activity/session-activity-view.ts b/ui/src/pages/activity/session-activity-view.ts index 48dbddd81fc5..e9539028863c 100644 --- a/ui/src/pages/activity/session-activity-view.ts +++ b/ui/src/pages/activity/session-activity-view.ts @@ -1,7 +1,9 @@ import { html, nothing } from "lit"; import type { GatewaySessionRow } from "../../api/types.ts"; import type { ApplicationContext } from "../../app/context.ts"; +import { icons } from "../../components/icons.ts"; import "../../components/viewer-facepile.ts"; +import "../../components/web-awesome-popover.ts"; import { renderSettingsStatus } from "../../components/settings-ui.ts"; import { t } from "../../i18n/index.ts"; import { formatRelativeTimestamp, formatTimeAgo } from "../../lib/format.ts"; @@ -29,6 +31,7 @@ import { type SessionActivityViewProps = { context: ApplicationContext; filters: SessionActivityFilters; + presenceViewers: readonly PresenceViewer[]; retainedIdentity: PresenceViewer | null; rows: readonly GatewaySessionRow[]; onFiltersChange: (filters: SessionActivityFilters) => void; @@ -41,6 +44,166 @@ const TIME_LABELS: Record = { all: "activityFeed.timeAll", }; +type ActivityPerson = PresenceViewer & { count: number; lastActiveAt: number }; + +function isUnresolvedPerson(person: PresenceViewer): boolean { + return !person.name && !person.email && presenceViewerLabel(person) === person.id; +} + +function compactPersonLabel(person: PresenceViewer): string { + return isUnresolvedPerson(person) && person.id.length > 8 + ? `${person.id.slice(0, 8)}…` + : presenceViewerLabel(person); +} + +function renderPersonAvatar(person: PresenceViewer, showPresence = false) { + if (isUnresolvedPerson(person)) { + return html``; + } + return html` + + ${showPresence && (person.entries?.length ?? 0) > 0 + ? html`` + : nothing} + `; +} + +function selectPerson(event: Event, props: SessionActivityViewProps, personId: string | null) { + if (event.currentTarget instanceof Element) { + event.currentTarget.closest("wa-popover")?.removeAttribute("open"); + } + props.onFiltersChange({ ...props.filters, personId }); +} + +function setPeopleExpanded(event: Event, expanded: boolean) { + if (event.currentTarget instanceof Element) { + event.currentTarget.parentElement + ?.querySelector(".activity-feed__people-trigger") + ?.setAttribute("aria-expanded", String(expanded)); + } +} + +function renderPersonRow(person: ActivityPerson, props: SessionActivityViewProps) { + const online = (person.entries?.length ?? 0) > 0; + return html``; +} + +function renderPeopleControl( + props: SessionActivityViewProps, + people: readonly ActivityPerson[], + selectedPerson: PresenceViewer | null, + totalSessions: number, +) { + const visible = people.slice(0, 3); + const overflow = people.length - visible.length; + const resolved = people.filter((person) => !isUnresolvedPerson(person)); + const unresolved = people.filter(isUnresolvedPerson); + return html`
+ + ${selectedPerson + ? html`` + : nothing} + setPeopleExpanded(event, true)} + @wa-hide=${(event: Event) => setPeopleExpanded(event, false)} + > +
+ + ${resolved.map((person) => renderPersonRow(person, props))} + ${unresolved.length > 0 + ? html` +
+ ${t("activityFeed.unresolvedIdentities")} +
+
+ ${unresolved.map((person) => renderPersonRow(person, props))} +
` + : nothing} +
+
+
`; +} + function navigateToSession(event: MouseEvent, context: ApplicationContext, row: GatewaySessionRow) { if (!shouldHandleNavigationClick(event)) { return; @@ -177,13 +340,22 @@ function renderIdentityHeader( export function renderSessionActivityView(props: SessionActivityViewProps) { const projection = projectSessionActivity(props.rows, props.filters); const identity = props.retainedIdentity; + const onlineById = new Map(props.presenceViewers.map((person) => [person.id, person])); + const people = projection.people.map((person) => { + const online = onlineById.get(person.id); + return online + ? { ...person, ...online, count: person.count, lastActiveAt: person.lastActiveAt } + : person; + }); + const selectedPerson = props.filters.personId + ? (people.find((person) => person.id === props.filters.personId) ?? identity) + : null; return html`
- +
+ ${renderPeopleControl(props, people, selectedPerson, projection.timeCount)} +
${props.filters.personId ? identity diff --git a/ui/src/pages/activity/session-activity.test.ts b/ui/src/pages/activity/session-activity.test.ts index 2d28d29a3798..637a4813e6f2 100644 --- a/ui/src/pages/activity/session-activity.test.ts +++ b/ui/src/pages/activity/session-activity.test.ts @@ -75,6 +75,34 @@ describe("session activity projection", () => { expect(filtered.timeCount).toBe(3); }); + it("orders people by their latest activity instead of session count or online state", () => { + const activity = projectSessionActivity( + [ + session("agent:main:older-1", "Older one", now - 3_000, { + id: "frequent", + label: "Frequent", + }), + session("agent:main:older-2", "Older two", now - 2_000, { + id: "frequent", + label: "Frequent", + }), + session("agent:main:newest", "Newest", now - 1_000, { + id: "recent", + label: "Recent", + }), + ], + { personId: null, query: "", time: "7d" }, + now, + ); + + expect( + activity.people.map(({ id, count, lastActiveAt }) => ({ id, count, lastActiveAt })), + ).toEqual([ + { id: "recent", count: 1, lastActiveAt: now - 1_000 }, + { id: "frequent", count: 2, lastActiveAt: now - 2_000 }, + ]); + }); + it("round-trips linkable filters in a stable query order", () => { const search = sessionActivitySearch({ personId: "profile/a", diff --git a/ui/src/pages/activity/session-activity.ts b/ui/src/pages/activity/session-activity.ts index a479a272fd86..d3e405bfdc2e 100644 --- a/ui/src/pages/activity/session-activity.ts +++ b/ui/src/pages/activity/session-activity.ts @@ -15,7 +15,7 @@ export type SessionActivityFilters = { time: ActivityTimeFilter; }; -type ActivityPerson = PresenceViewer & { count: number }; +type ActivityPerson = PresenceViewer & { count: number; lastActiveAt: number }; type SessionActivityDay = { key: string; @@ -129,10 +129,12 @@ function dayStart(timestamp: number): number { function projectPeople(rows: readonly GatewaySessionRow[]): ActivityPerson[] { const people = new Map(); for (const row of rows) { + const lastActiveAt = sessionActivityTimestamp(row); for (const actor of sessionActors(row)) { const existing = people.get(actor.id); if (existing) { existing.count += 1; + existing.lastActiveAt = Math.max(existing.lastActiveAt, lastActiveAt); continue; } people.set(actor.id, { @@ -141,13 +143,14 @@ function projectPeople(rows: readonly GatewaySessionRow[]): ActivityPerson[] { avatarUrl: normalized(actor.avatarUrl), watchedSessions: [], count: 1, + lastActiveAt, }); } } return [...people.values()].toSorted((a, b) => { - const countOrder = b.count - a.count; - if (countOrder !== 0) { - return countOrder; + const activityOrder = b.lastActiveAt - a.lastActiveAt; + if (activityOrder !== 0) { + return activityOrder; } const labelA = presenceViewerLabel(a).toLowerCase(); const labelB = presenceViewerLabel(b).toLowerCase(); diff --git a/ui/src/styles/activity.css b/ui/src/styles/activity.css index 5c253a7b7346..4588cd233569 100644 --- a/ui/src/styles/activity.css +++ b/ui/src/styles/activity.css @@ -55,8 +55,8 @@ openclaw-activity-page { } .activity-feed { - display: grid; - grid-template-columns: 204px minmax(0, 1fr); + display: flex; + flex-direction: column; width: 100%; height: 100%; min-height: 0; @@ -66,95 +66,192 @@ openclaw-activity-page { background: var(--card); } -.activity-feed__facets { - min-width: 0; - overflow-y: auto; - padding: var(--space-4) var(--space-3); - border-right: 1px solid var(--border); +.activity-feed__toolbar { + display: flex; + flex: 0 0 auto; + align-items: center; + gap: var(--space-2); + padding: var(--space-3) var(--space-4); + border-bottom: 1px solid var(--border); background: color-mix(in srgb, var(--bg-elevated) 58%, transparent); } .activity-feed__search { - display: grid; - gap: var(--space-2); - color: var(--muted); - font-size: var(--control-ui-text-xs); - font-weight: 650; - letter-spacing: 0.06em; - text-transform: uppercase; + position: relative; + flex: 1 1 240px; + min-width: 180px; +} + +.activity-feed__search svg { + position: absolute; + top: 50%; + left: 9px; + width: 14px; + height: 14px; + translate: 0 -50%; + fill: none; + stroke: var(--muted); + stroke-width: 1.8px; + pointer-events: none; } .activity-feed__search input { - width: 100%; - min-width: 0; - text-transform: none; - letter-spacing: normal; + height: var(--settings-control-height); + padding-left: 30px; } -.activity-feed__facet { - display: grid; - gap: 2px; - margin-top: var(--space-5); +.activity-feed__time-filter { + flex: 0 0 auto; } -.activity-feed__facet h2 { - margin: 0 0 var(--space-2); - padding-inline: var(--space-2); - color: var(--muted); - font-size: var(--control-ui-text-xs); - font-weight: 650; - letter-spacing: 0.08em; - text-transform: uppercase; -} - -.activity-feed__facet-option { +.activity-feed__people-control { + position: relative; display: flex; align-items: center; - gap: var(--space-2); - width: 100%; - min-width: 0; - padding: 6px 8px; - border: 0; - border-radius: var(--radius-md); - background: transparent; +} + +.activity-feed__people-trigger, +.activity-feed__people-clear { + height: var(--settings-control-height); color: var(--muted); - font: inherit; - font-size: var(--control-ui-text-sm); - text-align: left; - cursor: var(--cursor-action); } -.activity-feed__facet-option:hover, -.activity-feed__facet-option:focus-visible { - background: var(--bg-hover); - color: var(--text); +.activity-feed__people-trigger { + min-width: 64px; + max-width: 220px; + padding: 3px 10px; } -.activity-feed__facet-option[aria-pressed="true"] { - background: color-mix(in srgb, var(--accent) 12%, var(--bg-hover)); - color: var(--text-strong); - font-weight: 600; +.activity-feed__people-trigger:has(+ .activity-feed__people-clear) { + border-radius: var(--radius-md) 0 0 var(--radius-md); } -.activity-feed__facet-option > span:not(.viewer-avatar) { +.activity-feed__people-clear { + width: 28px; + padding: 0; + margin-left: -1px; + border-radius: 0 var(--radius-md) var(--radius-md) 0; + font-size: var(--control-ui-text-lg); +} + +.activity-feed__people-trigger:focus-visible, +.activity-feed__people-clear:focus-visible { + outline: none; + border-color: var(--ring); + box-shadow: var(--focus-ring); +} + +.activity-feed__selected-person { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.activity-feed__person .viewer-avatar { +.activity-feed__facepile { + display: inline-flex; + align-items: center; +} + +.activity-feed__facepile .activity-feed__person-avatar + .activity-feed__person-avatar, +.activity-feed__facepile .activity-feed__person-avatar + .viewer-avatar--overflow { + margin-left: -5px; +} + +.activity-feed__facepile .viewer-avatar { + border-color: var(--bg-elevated); +} + +.activity-feed__person-avatar { + position: relative; + display: inline-flex; flex: none; } -.activity-feed__facet-count { - margin-left: auto; +.activity-feed__presence-dot { + position: absolute; + right: -1px; + bottom: -1px; + width: 7px; + height: 7px; + border: 1.5px solid var(--bg-elevated); + border-radius: var(--radius-full); + background: var(--ok); +} + +.activity-feed__unknown-avatar { + flex: none; +} + +.activity-feed__unknown-avatar svg { + width: 12px; + height: 12px; +} + +.activity-feed__people-popover { + --max-width: min(360px, calc(100vw - 32px)); +} + +.activity-feed__people-popover::part(body) { + padding: 0; +} + +.activity-feed__people-panel { + width: min(340px, calc(100vw - 32px)); + max-height: min(480px, 70vh); + overflow-y: auto; + padding: var(--space-2); +} + +.activity-feed__people-row { + cursor: var(--cursor-action); +} + +.activity-feed__people-row[aria-pressed="true"] { + background: color-mix(in srgb, var(--accent) 12%, var(--bg-hover)); +} + +.activity-feed__people-copy { + display: flex; + flex: 1 1 auto; + min-width: 0; + gap: 4px; +} + +.activity-feed__people-name, +.activity-feed__last-active { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.activity-feed__people-name { + color: var(--text-strong); +} + +.activity-feed__last-active, +.activity-feed__people-count, +.activity-feed__people-group-label { color: var(--muted); + font-size: var(--control-ui-text-xs); +} + +.activity-feed__people-count { + margin-left: auto; font-variant-numeric: tabular-nums; } +.activity-feed__people-group-label { + padding: var(--space-1) var(--space-2); + font-weight: 650; + letter-spacing: 0.06em; + text-transform: uppercase; +} + .activity-feed__main { + flex: 1 1 auto; min-width: 0; + min-height: 0; overflow-y: auto; padding: var(--space-5); } @@ -521,15 +618,17 @@ openclaw-activity-page { @media (max-width: 768px) { .activity-feed { - display: block; height: auto; overflow: visible; } - .activity-feed__facets { - overflow: visible; - border-right: 0; - border-bottom: 1px solid var(--border); + .activity-feed__toolbar { + flex-wrap: wrap; + } + + .activity-feed__search { + flex-basis: 100%; + order: -1; } .activity-feed__main { diff --git a/ui/src/styles/components.css b/ui/src/styles/components.css index 8fbe590b179b..1a64a5450ea8 100644 --- a/ui/src/styles/components.css +++ b/ui/src/styles/components.css @@ -1120,7 +1120,8 @@ openclaw-session-progress-hovercard-provider { .field input, .field textarea, -.field select { +.field select, +.input { border: 1px solid var(--input); background: var(--card); border-radius: var(--radius-md); @@ -1133,21 +1134,25 @@ openclaw-session-progress-hovercard-provider { } .field input:not([type="checkbox"]):not([type="radio"]), -.field select { +.field select, +input.input:not([type="checkbox"]):not([type="radio"]), +select.input { height: 38px; } @media (hover: none) and (pointer: coarse) { .field input, .field textarea, - .field select { + .field select, + .input { font-size: 16px; } } .field input:focus-visible, .field textarea:focus-visible, -.field select:focus-visible { +.field select:focus-visible, +.input:focus-visible { border-color: var(--ring); box-shadow: var(--focus-ring); }