feat(web,hub): rich hover tooltips on session-list attention indicators#941
Merged
Merged
Conversation
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>
There was a problem hiding this comment.
Findings
- [Major] Tooltip content is not reachable from keyboard focus — the new tooltip puts
aria-describedby/aria-labelon an inner non-focusable<span>, while the actual focus target is the outer session-row<button>inSessionList.tsx. Because focus stays on the button,group-focus-withinon this descendant never matches, and screen readers focus the button without the new tooltip description. This regresses the stated accessible hover-copy path for both attention dots and the schedule clock. Evidence:web/src/components/HoverTooltip.tsx:40andweb/src/components/SessionList.tsx:672.
Suggested fix:// Have the focused row own the description and expose a parent-focus trigger. <button type="button" aria-describedby={tooltipId} className="group/session-row ..." > ... <HoverTooltip id={tooltipId} label={scheduledLabel} target={<ScheduleIcon className="h-3.5 w-3.5 text-[var(--app-hint)]" />} revealOnParentFocusClass="group-focus-visible/session-row:opacity-100 group-focus-visible/session-row:visible" > ... </HoverTooltip> </button>
Questions
- None.
Summary
- Review mode: initial
- One accessibility regression found in the new tooltip primitive/usage. Residual risk: I did not run the full test suite in this automation pass.
Testing
- Not run (automation)
HAPI Bot
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>
There was a problem hiding this comment.
Findings
- No issues found.
Questions
- None.
Summary
- Review mode: follow-up after new commits
- No issues found in the latest full PR diff. The previous bot finding about tooltip keyboard/focus ownership appears addressed by moving
aria-describedbyto the session row and exposing parent focus-visible reveal classes. Residual risk: I did not run the full test suite in this automation pass.
Testing
- Not run (automation)
HAPI Bot
5 tasks
yoyoworms
added a commit
to yoyoworms/hapi
that referenced
this pull request
Jun 20, 2026
…ate, drag-drop Merge tiann/hapi upstream (33 commits, v0.20.2) into the liuxin fork. Notable adoptions: - file viewer download button (tiann#926) — the download UI the user asked about; decodes existing base64 content to a Blob, no new endpoint - Claude Code 'auto' permission mode (tiann#879) - in-app PWA update prompt when a new service worker is ready (tiann#946) — PwaUpdateBannerWithStatusOffset, adopted WITHOUT enabling voice - drag-and-drop files onto chat to attach (tiann#936) + Web Share Target (tiann#933) - Codex fast mode / service-tier toggle (tiann#904), session service_tier column + merge-preserve - four hub-restart-cascade cleanup bugs (tiann#923), stale-PID detection after abnormal runner shutdown (tiann#931), OutgoingMessageQueue flush before next turn (tiann#909), inactive-session send error surface (tiann#922) - OLED black theme + per-appearance custom colors (tiann#937), session-list attention tooltips (tiann#941), active-only filter + paginated Show-N-more (tiann#903), file-explorer state persistence (tiann#911) Conflict resolutions (22 files) — fork features preserved: - store/index.ts: both forks bumped schema to V10 with different migrateFromV9ToV10; combined into one migration doing BOTH our content_uuid dedup index (messages) AND upstream service_tier (sessions) - socket/server.ts: collapsed duplicate maxHttpBufferSize key into Math.max(SOCKET_MAX_HTTP_BUFFER_SIZE, 100MB) — keeps our 100MB upload floor while honoring upstream's image-buffer constant - spawnSession plumbing (apiMachine/rpcGateway/syncEngine): unioned our sandbox+continueLatest with upstream's serviceTier across signature, RPC payload, and the resume call site - App.tsx: kept signOut + voice-disabled; adopted PWA update banner (PwaUpdateProvider auto-merged into App() wrapper) - SessionChat: nested both wrappers — HappyChatProvider > ShareSeedConsumer + DragDropZone > thread - sessionModel.test: split the two same-named merge tests (our codex dedup + upstream service-tier) back into separate it() blocks - useSendMessage: kept fork queue-stop AND upstream onError surface; keyed onError by non-null sid - startHappyServer: kept fork's manual-rename skip in change_title - schemas/locales/StatusBar/HappyComposer: unioned both sides Post-merge type fixes: pinned-session test mock gained pendingRequests + nextScheduledAt (new required SessionSummary fields); legacy-CLI model backfill in applySessionConfig now extracts modelId from upstream's Pi {provider,modelId} union. Validation: typecheck green (cli/web/hub); full suite 2260 tests pass incl. the runner stress test. via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to #698 / #699: replace generic native
titletooltips on session-list attention dots and the schedule clock with rich, accessible hover copy.HoverTooltip— lightweight CSS-driven tooltip primitive (no portal/positioning JS).pendingRequests[]onSessionSummary— capped (5), oldest-first structured slice of pending tool requests so permission/input dots can list actual tool names without a per-row detail fetch. Additive; older hubs degrade gracefully.+N moreoverflow when single-kind); background shows task count; unread shows title only (relative time already on the row).nextScheduledAt(MIN futurescheduled_atper session); tooltip shows relative + absolute fire time (e.g. "Fires in 26m · Jun 17, 12:06 PM") and "+N more" when multiple are queued.Closes #940
Test plan
bun run typecheck(cli + web + hub)bun test shared/src/sessionSummary.test.tsnpx vitest run web/src/components/SessionAttentionIndicator.test.tsxnpx vitest run web/src/lib/scheduledTime.test.tsbun test hub/src/store/messages.test.ts(batchminFutureScheduledAtBySessionIds)Made with Cursor