Skip to content

fix(ui): use DS::Tooltip and DS::Pill for the insights nav badge#2805

Open
tryeverything24 wants to merge 1 commit into
we-promise:mainfrom
tryeverything24:fix/ds-tooltip-pill-insights-nav-badge
Open

fix(ui): use DS::Tooltip and DS::Pill for the insights nav badge#2805
tryeverything24 wants to merge 1 commit into
we-promise:mainfrom
tryeverything24:fix/ds-tooltip-pill-insights-nav-badge

Conversation

@tryeverything24

@tryeverything24 tryeverything24 commented Jul 25, 2026

Copy link
Copy Markdown

PR body — we-promise/sure — account: tryeverything24 (PR #2 on this repo)

Branch: fix/ds-tooltip-pill-insights-nav-badge (in WSL clone /root/sure, commit 961b3898, based on origin/main @ 6b6ae0ca)
Title: fix(ds): use DS::Tooltip and DS::Pill for the insights nav badge


Summary

Addresses Finding 2 of the DS Drift Patrol report in #2745.

The insights nav icon (added in #2550, app/views/layouts/application.html.erb) hand-rolled both of its affordances:

  1. a raw title: attribute on the icon-only link as its tooltip, and
  2. a raw rounded-full span with fixed sizing as the unread-count badge.

What changed

  • Tooltip: the raw title= is replaced by DS::Tooltip with as: :span — the component's documented mode for triggers inside an already-focusable interactive ancestor (here, the nav link). Same conversion the repo already merged for the accounts eye-off indicator. The link keeps its aria: { label: }, so the accessible name is unchanged.
  • Badge: the unread count renders through DS::Pill (badge mode, style: :filled, :neutral tone → dark chip with white label, closest DS equivalent to the previous bg-inverse/text-inverse). Positioned via an absolute wrapper span at the icon corner, following the layouts/shared/_nav_item.html.erb precedent (which wraps its preview DS::Pill the same way); nudged to -top-1 -right-1 so the DS-scale pill doesn't cover the lightbulb.
  • The "9+" overflow cap and the unread_insights_count.positive? guard are untouched; the turbo-prefetch opt-out and its explanatory comment are untouched.

Notes for review

  • Hover shows the DS tooltip anchored to the icon (floating-ui), replacing the browser-native title bubble. Keyboard focus behavior is unchanged from title= (native titles never showed on focus either; screen readers get the retained aria-label).
  • No new locale keys: the tooltip reuses layouts.application.insights.

Test plan

  • bin/rails test — full suite green (5,920 runs, 23,808 assertions, 0 failures, 0 errors).
  • bin/rails test test/controllers/insights_controller_test.rb — green (covers the layout's unread-badge path).
  • bundle exec erb_lint app/views/layouts/application.html.erb — no offenses.
  • bin/rubocop — clean; bin/brakeman — no warnings.

Checklist for opening: base = main, allow edits from maintainers ON. Do NOT use closing keywords (#2745's Finding 1 is covered separately) — reference it as "Addresses Finding 2 of #2745".

Summary by CodeRabbit

  • UI Improvements
    • Updated the desktop Insights navigation control with an improved lightbulb tooltip.
    • Refined the unread insights indicator with a consistent pill-style badge, displaying the exact count or 9+ for larger totals.

The insights nav icon hand-rolled both of its affordances: a raw title=
attribute as the tooltip and a rounded-full span as the unread-count
badge. Route both through the design system: DS::Tooltip (as: :span,
since the trigger sits inside the already-focusable link) replaces the
title= attribute, and DS::Pill (filled neutral badge) renders the unread
count. The link keeps its aria-label, so the accessible name is
unchanged.

Addresses Finding 2 of the DS Drift Patrol report in we-promise#2745.
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 59e61c7f-fa86-4475-b5c9-3a25ceaaa0b8

📥 Commits

Reviewing files that changed from the base of the PR and between 6b6ae0c and 961b389.

📒 Files selected for processing (1)
  • app/views/layouts/application.html.erb

📝 Walkthrough

Walkthrough

The desktop Insights navigation control now wraps its lightbulb icon with DS::Tooltip and renders positive unread counts with DS::Pill, including capped 9+ display behavior.

Changes

Insights navigation

Layer / File(s) Summary
Tooltip and unread indicator rendering
app/views/layouts/application.html.erb
The Insights link retains its ARIA label and disabled Turbo prefetch, while the icon uses DS::Tooltip and the unread count uses DS::Pill with updated positioning.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested labels: contributor:verified, pr:verified

Suggested reviewers: jjmata

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main UI change: replacing the insights nav tooltip and badge with design-system components.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 961b389846

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

<%# `as: :span` because the trigger sits inside this already-
focusable link — the tooltip replaces the raw title=
attribute; the link keeps its aria-label. %>
<%= render DS::Tooltip.new(text: t("layouts.application.insights"), icon: "lightbulb", size: "sm", as: :span) %>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bind the tooltip trigger to the full link

When the pointer is over the link's padding—or the link receives keyboard focus—this tooltip never opens because DS::Tooltip listens on its own descendant span; focus events do not propagate down from the <a>, and its controller only binds ancestor listeners for <summary> elements (app/components/DS/tooltip_controller.js:29-54). Consequently, replacing the link's title limits the label to hovering directly over the small lightbulb glyph instead of the full 36/44px hit target and provides no focus-triggered tooltip. The tooltip trigger/controller needs to encompass or explicitly bind to the link.

Useful? React with 👍 / 👎.

jjmata commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

Reviewed the diff against the DS component APIs (app/components/DS/tooltip.rb, tooltip.html.erb, tooltip_controller.js, pill.rb, pill.html.erb) and the frontend conventions in CLAUDE.md.

Likely bug: keyboard focus won't reveal the new tooltip

app/views/layouts/application.html.erb (~line 172) renders DS::Tooltip.new(..., as: :span) nested inside the link_to insights_path anchor. The inline comment justifies this as the same pattern the component doc describes for :span mode ("the ancestor's focus still triggers the tooltip because focusin bubbles up to the Stimulus controller").

That guarantee doesn't actually hold here. app/components/DS/tooltip_controller.js line 49 hardcodes the ancestor lookup to <summary> only:

this.summaryAncestor = this.element.closest("summary");
if (this.summaryAncestor) {
  this.summaryAncestor.addEventListener("focusin", this.show);
  ...
}

The Insights trigger's ancestor is an <a>, not a <summary>, so closest("summary") returns null and no focusin/focusout/keydown listeners get attached to the link. Since focusin bubbles up from the focused element (the <a>) through its ancestors — not down into its descendant <span> — the tooltip controller's own focusin listener on this.element never fires when the link is Tab-focused. Net effect: keyboard users tabbing to the Insights icon get the aria-label read by AT (fine), but sighted keyboard users lose the visual tooltip entirely, and Esc-to-dismiss never applies either — regressing from the old title= attribute, which browsers do show on keyboard focus.

Worth confirming with a manual keyboard pass before merge. Fix would likely be generalizing the controller's ancestor lookup (e.g. closest('summary, a, button, [tabindex]')) rather than hardcoding summary, or reworking this specific call site.

Everything else looks clean:

  • DS::Pill.new(label:, tone: :neutral, style: :filled, marker: false) — valid params; :neutral resolves via SEMANTIC_TONE_ALIASES to :gray, marker: false is correct for a numeric badge (vs. the uppercase "marker" mode), :filled is a valid STYLES entry.
  • No raw Tailwind colors — all color/tone logic stays inside the DS components' functional tokens.
  • icon helper isn't called directly anywhere new; it's routed through the components as intended.
  • No new user-facing strings — reuses the existing t("layouts.application.insights") key, so no en.yml changes needed.
  • aria-label is correctly retained on the link after removing the redundant title= attribute.

Generated by Claude Code

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.

2 participants