From 04eae7cea17e42d5d8ea9779e4ccdfd1fdbb9f7e Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 23 Aug 2026 11:58:59 -0700 Subject: [PATCH] feat(ui): link session hovercard identities to their activity feed (#128313) * feat(ui): link session hovercard identities to their activity feed Names shown in the session hovercard are now links to that person's Activity feed. `activityPersonLocation` in app-route-paths owns the `person` query contract that the Activity filters parse and the sidebar ONLINE list already hand-rolled, so every identity surface builds the same link. The creator avatar gets a decorative twin link (aria-hidden, out of the tab order) and participant names keep the locale's own "with {name}" phrasing and list separators while each name becomes its own link. * test(ui): capture the hovercard identity at rest for PR proof --- ui/src/app-route-paths.ts | 13 ++ ui/src/components/app-sidebar-render.ts | 7 +- ui/src/components/session-hovercard.test.ts | 72 +++++++ ui/src/components/session-hovercard.ts | 187 +++++++++++++----- .../session-progress-hovercard.runtime.ts | 27 ++- .../session-hovercard-identity.e2e.test.ts | 147 ++++++++++++++ ui/src/pages/activity/session-activity.ts | 5 +- ui/src/styles/chat/progress-card.css | 16 ++ 8 files changed, 421 insertions(+), 53 deletions(-) create mode 100644 ui/src/e2e/session-hovercard-identity.e2e.test.ts diff --git a/ui/src/app-route-paths.ts b/ui/src/app-route-paths.ts index ec90a8be9527..928aaa53683a 100644 --- a/ui/src/app-route-paths.ts +++ b/ui/src/app-route-paths.ts @@ -107,6 +107,19 @@ export function pathForRoute(routeId: RouteId, basePath = ""): string { return normalizedBasePath ? `${normalizedBasePath}${path}` : path; } +/** Query key the Activity feed reads to scope its session list to one person. */ +export const ACTIVITY_PERSON_PARAM = "person"; + +/** Activity feed scoped to one person, for every surface that shows an identity. */ +export function activityPersonLocation( + personId: string, + basePath = "", +): { pathname: string; search: string; href: string } { + const pathname = pathForRoute("activity", basePath); + const search = `?${new URLSearchParams({ [ACTIVITY_PERSON_PARAM]: personId }).toString()}`; + return { pathname, search, href: `${pathname}${search}` }; +} + export function pathForWorkboardBoard(boardId: string, basePath = ""): string { if (!isValidWorkboardBoardId(boardId)) { throw new Error("Invalid Workboard board id."); diff --git a/ui/src/components/app-sidebar-render.ts b/ui/src/components/app-sidebar-render.ts index 2c0b06535928..f8edef660686 100644 --- a/ui/src/components/app-sidebar-render.ts +++ b/ui/src/components/app-sidebar-render.ts @@ -5,7 +5,7 @@ import { type NavigationRouteId, type SidebarZoneEntry, } from "../app-navigation.ts"; -import { isRouteId, isSessionRouteId, pathForRoute } from "../app-route-paths.ts"; +import { activityPersonLocation, isRouteId, isSessionRouteId } from "../app-route-paths.ts"; import { resolveControlUiAuthToken } from "../app/control-ui-auth.ts"; import { isNativeWebChromeHost } from "../app/native-web-chrome.ts"; import { readPresenceEntries, resolveCurrentSelfUser } from "../app/user-profile.ts"; @@ -315,14 +315,13 @@ export function renderAppSidebarOnline(host: AppSidebarRenderHost) { ? nothing : html`