Skip to content

feat(web): rich hover tooltips on session-list attention indicators#45

Closed
heavygee wants to merge 4 commits into
mainfrom
feat/session-list-rich-tooltips
Closed

feat(web): rich hover tooltips on session-list attention indicators#45
heavygee wants to merge 4 commits into
mainfrom
feat/session-list-rich-tooltips

Conversation

@heavygee

Copy link
Copy Markdown
Owner

Summary

The session-row attention dots and future-scheduled clock icon (added in tiann#699) currently use plain title="" attributes which only surface a one-word label ("Permission required"). Replace those with hover/focus-revealed tooltips that explain why the indicator is showing — naming the pending tools, counting background tasks, surfacing the "updated Nm ago" timestamp, and explaining the pending schedule.

To make per-tool copy possible without a follow-up GET /sessions/:id round trip, SessionSummary now carries a structured slice of the pending tool requests, capped at PENDING_REQUEST_SUMMARY_CAP = 5 oldest-first:

pendingRequests: Array<{
  id: string
  kind: 'permission' | 'input'
  tool: string
  since: number
}>

pendingRequestsCount remains the authoritative total; pendingRequestKinds is still derived from the FULL request set so a single 'input' request beyond the cap still correctly surfaces its kind on the session row.

The tooltip primitive (HoverTooltip) is a CSS-driven reveal — no portal, no positioning JS, no new dependency — so it composes cheaply inside the existing session-row <button> and stays out of the way on touch devices, which keep getting the same aria-label the old title="" attribute provided to screen readers.

What you'll see

  • Permission dot: hover shows "Permission required" + "Approve:" + bulleted list of pending tool names. "+N more" only appears when all pending requests share that kind (mixed-kind sessions don't lie about per-kind overflow).
  • Input dot: same, with "Reply to:".
  • Background dot: "Background tasks running" + "N tasks running".
  • Unread dot: "New activity" + "Updated Nm ago".
  • Schedule clock: "Scheduled message pending" + "Will fire when due."

Files

  • `shared/src/sessionSummary.ts` - new `PendingRequest` type, `getPendingRequests` derivation with stable sort (createdAt asc, id tiebreak), and `PENDING_REQUEST_SUMMARY_CAP`. `getPendingRequestKinds` refactored to share the kind classifier but still walks the full request set.
  • `web/src/components/HoverTooltip.tsx` - new generic CSS-only hover/focus tooltip primitive.
  • `web/src/components/SessionAttentionIndicator.tsx` - takes `summary` prop now, composes tooltip body from the new structured field. Mixed-kind overflow suppression noted inline.
  • `web/src/components/SessionList.tsx` - wraps the schedule clock in `HoverTooltip`; passes `summary` to the indicator.
  • `web/src/lib/relativeTime.ts` - extracted from `SessionList.tsx` to break a circular import (the indicator now needs it for the unread tooltip).
  • `web/src/lib/locales/{en,zh-CN}.ts` - `session.tooltip.*` keys for both locales.
  • Tests: shared derivation + cap + tie-break + full-set kind behaviour; web tooltip render across all four attention kinds, mixed-kind overflow suppression, and aria-label exposure.

Compatibility

  • `pendingRequests` is additive on `SessionSummary`. Older clients that only read existing fields are unaffected.
  • The web reads it defensively (`summary.pendingRequests ?? []`) so a hub that hasn't been updated yet would just show the old single-line tooltip with no detail body.

Test plan

  • `bun test` in `shared/` (10 new derivation tests + existing pass)
  • `npx vitest run` in `web/` (970 -> 971 tests; 7 new tooltip tests in `SessionAttentionIndicator.test.tsx`)
  • `bun run typecheck` in `hub/` (clean)
  • `npx tsc --noEmit` in `web/` (clean)
  • Manual smoke on a live hub with 1, 5, and 6+ pending requests of mixed kinds

Disclosure

Drafted with `claude-opus-4.7-thinking-high` via Cursor; reviewed and tested by the human contributor.

Made with Cursor

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

heavygee and others added 3 commits June 17, 2026 12:13
The session-row attention dots and the future-scheduled clock icon used
plain `title=""` attributes which gave only a one-word label ("Permission
required"). Replace those with hover/focus-revealed tooltips that name
*which* tools are blocking, count background tasks, surface the
"updated Nm ago" timestamp, and explain the pending schedule.

To make per-tool copy possible without an extra round trip,
`SessionSummary` now carries a structured slice of the pending tool
requests, capped at `PENDING_REQUEST_SUMMARY_CAP = 5` oldest-first:

  pendingRequests: Array<{ id; kind; tool; since }>

`pendingRequestsCount` remains the authoritative total;
`pendingRequestKinds` is still derived from the FULL request set so a
single `'input'` request beyond the cap still surfaces its kind on the
session row.

The tooltip primitive (`HoverTooltip`) is a CSS-driven reveal — no
portal, no positioning JS — so it composes cheaply inside the existing
session-row `<button>` and stays out of the way on touch devices, which
keep getting the same `aria-label` the old `title=""` attribute provided
to screen readers.

Test coverage: shared derivation + cap + tie-break + full-set kind
behaviour; web tooltip render across all four attention kinds plus
mixed-kind overflow suppression and aria-label exposure.

Co-authored-by: Cursor <cursoragent@cursor.com>
Two operator-feedback fixes on the new session-list HoverTooltip:

1. Tooltip background was bg-[var(--app-bg)] - the same variable as the
   session row underneath - so the tooltip looked translucent and the row
   text bled through. Switch to bg-[var(--app-secondary-bg)] (#2C2C2E
   dark / #f3f4f6 light, both opaque) and bump shadow-md -> shadow-lg.
   Telegram-themed clients still pick up tg-theme-secondary-bg-color so
   the tooltip stays on-theme.

2. The 'unread' attention dot tooltip rendered 'New activity / Updated 5m
   ago', but the relative-time pill ('5m ago') is already on the right
   edge of the same session row. The tooltip body just duplicated info.
   Render only the title for the unread case; drop the
   session.tooltip.unread.body i18n key from en + zh-CN.

The other tooltip kinds (permission/input list tools, background lists
task count) keep their bodies - those facts are not visible elsewhere on
the row.

Co-authored-by: Cursor <cursoragent@cursor.com>
The schedule clock tooltip previously said only "Will fire when due."
while the row already showed a relative updated-at pill. Extend the
session-list API with nextScheduledAt (MIN future scheduled_at per
session, same filter as futureScheduledMessageCount) and render:

- single scheduled: "Fires in 5m · Jun 16, 1:45 PM"
- multiple: "Next in 5m · Jun 16, 1:45 PM · +2 more"

Extract formatScheduledTime from QueuedMessagesBar into web/lib/
scheduledTime.ts alongside formatFutureRelativeTime and the tooltip
composer. SSE upsert preserves nextScheduledAt until the list refetch
that already runs on schedule-related events.

Co-authored-by: Cursor <cursoragent@cursor.com>
Address PR tiann#941 Major review: aria-describedby and tooltip visibility
were on a non-focusable inner span, so keyboard users tabbing the session
row button never received the rich tooltip description and
group-focus-within never matched.

- Session row button owns aria-describedby (attention + schedule ids)
- Add group/session-row + SESSION_ROW_TOOLTIP_FOCUS_CLASS reveal on
  :focus-visible
- HoverTooltip takes required id; drop inner aria-label/describedby
- useSessionRowTooltipIds helper composes stable row tooltip ids
- Tests for id wiring and parent-focus reveal classes

Co-authored-by: Cursor <cursoragent@cursor.com>
@heavygee

Copy link
Copy Markdown
Owner Author

Superseded by upstream tiann#941 (merged path). Closing fork dogfood PR.

@heavygee heavygee closed this Jun 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant