Skip to content

feat: add severity tooltips in HTML report#86

Merged
let-sunny merged 12 commits intomainfrom
feat/severity-tooltips
Mar 26, 2026
Merged

feat: add severity tooltips in HTML report#86
let-sunny merged 12 commits intomainfrom
feat/severity-tooltips

Conversation

@let-sunny
Copy link
Copy Markdown
Owner

@let-sunny let-sunny commented Mar 25, 2026

Summary

Hovering over the severity badges (Blocking / Risk / Missing Info / Suggestion) in the issue summary section now shows a tooltip explaining what each level means.

  • Blocking: Cannot implement correctly without fixing
  • Risk: Implementable now but will break later
  • Missing Info: Information absent, AI must guess
  • Suggestion: Not problematic, but improves systemization

Uses CSS-only tooltip (group-hover + opacity transition), plus title attribute as fallback.

Test plan

  • 590 tests pass
  • Visual: hover over each severity badge in /tmp/report-tooltip.html

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added hover tooltips for category labels and severity indicators with richer explanatory text and consistent tooltip behavior.
  • Style
    • Redesigned category view from a clickable grid to a non-clickable vertical list with inline percentage bars and simplified issue counts.
    • Adjusted responsive layout, spacing, and score alignment for improved readability; added row hover styling and compact tooltip visuals.

Hovering over Blocking/Risk/Missing Info/Suggestion badges in the
issue summary section now shows a tooltip explaining what each
severity level means.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 25, 2026

📝 Walkthrough

Walkthrough

Converted clickable category cards into non-clickable vertical rows with inline percentage bars, moved category and severity tooltip text into data-tip/mappings, added a JS tooltip implementation and CSS tooltip styles, and updated web/report renderers to emit constrained tooltip markup.

Changes

Cohort / File(s) Summary
Figma plugin UI
app/figma-plugin/src/ui.template.html
Replaced clickable .cat-card grid with non-clickable .cat-row entries: removed data-cat/onclick and active toggling, display counts without “issues”, add .cat-bar/.cat-bar-fill percentage bars, populate data-tip from catTips, and add JS tooltip DOM creation/positioning logic.
Shared styles
app/shared/styles.css
Converted 3-column grid to vertical flex list (.cat-row), added .cat-bar/.cat-bar-fill, adjusted .cat-label and .cat-score sizing/alignment, removed .cat-card hover/active rules, and introduced .cic-tooltip & .cic-tooltip-arrow styles.
Web UI gauges
app/web/src/index.html
Changed responsive grid from sm:grid-cols-6sm:grid-cols-5. Added per-category hover tooltips using CATEGORY_DESCRIPTIONS (escaped) and enhanced severity dot output to include a hover tooltip wrapper with escaped tip text.
Report HTML summary
src/core/report-html/index.ts
Added SEVERITY_TOOLTIPS mapping and constrained tooltip width (max-w-[220px] w-max). Updated renderSummaryDot to include relative group cursor-help, a title attribute (escaped), and a hidden hover-revealed tooltip element containing the escaped tooltip.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I traded cards for tidy rows,

a sliver of bar where numbers show,
little tips that softly peep,
hover close — the secrets keep,
I hop away, content and slow.

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title specifically mentions adding severity tooltips in HTML report, which is clearly reflected in the src/core/report-html/index.ts changes that implement SEVERITY_TOOLTIPS mapping and tooltip rendering.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/severity-tooltips

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

let-sunny and others added 8 commits March 26, 2026 08:13
Same tooltip descriptions as HTML report:
- Web app: CSS tooltip + title fallback (same as report)
- Figma plugin: title attribute (CSS tooltips limited in plugin iframe)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Web app: grid-cols-6 → grid-cols-5 for 5 categories
- Shared styles (Figma plugin): grid → flexbox with justify-content: center
  so the 2-item second row is centered instead of left-aligned

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace 3+2 card grid with vertical list layout (cleaner for 5 categories)
- Each row: score + label + progress bar + issue count
- Add category tooltips on hover (explains what each dimension measures)
- Progress bar color matches score (green/amber/red)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ltips

- Remove onclick from category rows (was awkwardly linked to filter tabs)
- Merge individual cat-card boxes into one bordered container with dividers
- Hover shows subtle background + tooltip with category description
- Severity tooltips already present from previous commit

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Figma plugin iframe sandbox suppresses native title tooltips.
Use data-tip attribute + CSS ::after pseudo-element instead.

Applies to both category rows and severity badges.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CSS ::after tooltips don't work: cat-grid overflow:hidden clips them,
  and Figma iframe may suppress them
- Switch to JS tooltip: createElement + mouseover positioning
- Clamp left position to prevent viewport overflow
- Flip below element when near top edge
- Remove overflow:hidden from cat-grid

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rigger

- Add triangle arrow (rotated square) pointing at hovered element
- Arrow centered on element's horizontal midpoint
- Tooltip flips below when near top edge
- Category tooltip triggers on label text only, not entire row
- Both tooltip and arrow share same show/hide lifecycle

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rrows

Web app now uses identical tooltip markup as the HTML report:
- Category gauges: group-hover tooltip with description + triangle arrow
- Severity badges: group-hover tooltip with description + triangle arrow
- Both use hidden → group-hover:block pattern (Tailwind CSS only, no JS)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@let-sunny let-sunny marked this pull request as ready for review March 25, 2026 23:37
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/figma-plugin/src/ui.template.html`:
- Around line 360-382: The tooltip can stick if the pointer leaves the iframe
because the current mouseout handler only hides when moving to another in-doc
element; update the listeners so the hide() function is also invoked when the
pointer leaves the plugin bounds — e.g., add a
document.addEventListener('mouseleave', hide) and/or modify the existing
document.addEventListener('mouseout', ...) to also call hide() when
e.relatedTarget is null (or not inside the iframe) so hide() (the function
defined at the top) always runs when the cursor exits the frame.
- Around line 153-166: The code still references the removed .cat-card and
data-cat in the filterCategory() logic; remove the dead branch that
queries/toggles '.cat-card' and 'data-cat' and update filterCategory() to
operate on the new category DOM structure (use '.cat-row' or '.cat-label' and
the category identity from CATEGORY_LABELS or the category key) so category
toggling/highlighting targets the elements actually rendered in the new map (see
cat-row, cat-label, cat-bar, and the scores.byCategory usage).

In `@app/web/src/index.html`:
- Around line 359-363: The tooltip markup built in the anchor HTML string (the
concatenation that uses renderGaugeSvg(cs.percentage, 100, 7), CATEGORY_LABELS,
CATEGORY_DESCRIPTIONS/desc and the inner tooltip <div> with class "absolute ...
whitespace-nowrap") prevents wrapping and causes off-screen bubbles; remove
"whitespace-nowrap" and add a max-width plus wrapping classes (for example
"max-w-xs break-words" or "max-w-[200px] break-words") to that tooltip <div> so
long copy wraps on narrow viewports, and apply the identical change to the
second tooltip template elsewhere in the same file.

In `@src/core/report-html/index.ts`:
- Around line 205-210: Create a single shared constant for severity tooltips
alongside SEVERITY_LABELS and replace the local SEVERITY_TOOLTIPS with an import
of that shared constant; ensure the shared mapping uses the canonical keys and
wording (e.g., "Missing Info" vs "Missing") so all surfaces match, then update
the other duplicated instances (the HTML/template copies referenced) to consume
the same shared constant or their build-time equivalent so the texts are no
longer duplicated and remain consistent across SEVERITY_TOOLTIPS and
SEVERITY_LABELS consumers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a027f408-0ced-4eb4-bdad-3e215a8b14fe

📥 Commits

Reviewing files that changed from the base of the PR and between f01b8e2 and b46906a.

📒 Files selected for processing (4)
  • app/figma-plugin/src/ui.template.html
  • app/shared/styles.css
  • app/web/src/index.html
  • src/core/report-html/index.ts

Comment on lines +360 to +382
function hide() { tip.classList.remove('visible'); arrow.classList.remove('visible'); }

document.addEventListener('mouseover', function(e) {
var el = e.target.closest('[data-tip]');
if (!el) { hide(); return; }
tip.textContent = el.getAttribute('data-tip');
tip.classList.add('visible');
var rect = el.getBoundingClientRect();
var tipH = tip.offsetHeight;
var tipW = tip.offsetWidth;
var cx = rect.left + rect.width / 2;
var tipLeft = Math.max(4, Math.min(cx - tipW / 2, window.innerWidth - tipW - 4));
var above = rect.top - tipH - 10 >= 0;
var tipTop = above ? rect.top - tipH - 10 : rect.bottom + 10;
tip.style.left = tipLeft + 'px';
tip.style.top = tipTop + 'px';
arrow.style.left = (cx - 4) + 'px';
arrow.style.top = (above ? rect.top - 14 : rect.bottom + 6) + 'px';
arrow.classList.add('visible');
});
document.addEventListener('mouseout', function(e) {
if (!e.target.closest('[data-tip]')) hide();
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hide the tooltip when the pointer leaves the plugin.

The current mouseout path only hides after another in-document hover target is processed. If the cursor exits the iframe directly from a [data-tip] element, the last tooltip stays stuck onscreen until the mouse re-enters.

🐛 Suggested fix
-      document.addEventListener('mouseout', function(e) {
-        if (!e.target.closest('[data-tip]')) hide();
-      });
+      document.addEventListener('mouseout', function(e) {
+        var to = e.relatedTarget && e.relatedTarget.closest ? e.relatedTarget.closest('[data-tip]') : null;
+        if (!to) hide();
+      });
+      document.documentElement.addEventListener('mouseleave', hide);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function hide() { tip.classList.remove('visible'); arrow.classList.remove('visible'); }
document.addEventListener('mouseover', function(e) {
var el = e.target.closest('[data-tip]');
if (!el) { hide(); return; }
tip.textContent = el.getAttribute('data-tip');
tip.classList.add('visible');
var rect = el.getBoundingClientRect();
var tipH = tip.offsetHeight;
var tipW = tip.offsetWidth;
var cx = rect.left + rect.width / 2;
var tipLeft = Math.max(4, Math.min(cx - tipW / 2, window.innerWidth - tipW - 4));
var above = rect.top - tipH - 10 >= 0;
var tipTop = above ? rect.top - tipH - 10 : rect.bottom + 10;
tip.style.left = tipLeft + 'px';
tip.style.top = tipTop + 'px';
arrow.style.left = (cx - 4) + 'px';
arrow.style.top = (above ? rect.top - 14 : rect.bottom + 6) + 'px';
arrow.classList.add('visible');
});
document.addEventListener('mouseout', function(e) {
if (!e.target.closest('[data-tip]')) hide();
});
function hide() { tip.classList.remove('visible'); arrow.classList.remove('visible'); }
document.addEventListener('mouseover', function(e) {
var el = e.target.closest('[data-tip]');
if (!el) { hide(); return; }
tip.textContent = el.getAttribute('data-tip');
tip.classList.add('visible');
var rect = el.getBoundingClientRect();
var tipH = tip.offsetHeight;
var tipW = tip.offsetWidth;
var cx = rect.left + rect.width / 2;
var tipLeft = Math.max(4, Math.min(cx - tipW / 2, window.innerWidth - tipW - 4));
var above = rect.top - tipH - 10 >= 0;
var tipTop = above ? rect.top - tipH - 10 : rect.bottom + 10;
tip.style.left = tipLeft + 'px';
tip.style.top = tipTop + 'px';
arrow.style.left = (cx - 4) + 'px';
arrow.style.top = (above ? rect.top - 14 : rect.bottom + 6) + 'px';
arrow.classList.add('visible');
});
document.addEventListener('mouseout', function(e) {
var to = e.relatedTarget && e.relatedTarget.closest ? e.relatedTarget.closest('[data-tip]') : null;
if (!to) hide();
});
document.documentElement.addEventListener('mouseleave', hide);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/figma-plugin/src/ui.template.html` around lines 360 - 382, The tooltip
can stick if the pointer leaves the iframe because the current mouseout handler
only hides when moving to another in-doc element; update the listeners so the
hide() function is also invoked when the pointer leaves the plugin bounds —
e.g., add a document.addEventListener('mouseleave', hide) and/or modify the
existing document.addEventListener('mouseout', ...) to also call hide() when
e.relatedTarget is null (or not inside the iframe) so hide() (the function
defined at the top) always runs when the cursor exits the frame.

Comment on lines +205 to +210
const SEVERITY_TOOLTIPS: Record<string, string> = {
Blocking: "Cannot implement correctly without fixing. Direct impact on screen reproduction.",
Risk: "Implementable now but will break or increase cost later.",
"Missing Info": "Information is absent, forcing AI to guess.",
Suggestion: "Not immediately problematic, but improves systemization.",
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Move the severity tooltip text into shared constants.

These definitions are now duplicated here, in app/web/src/index.html (Lines 392-393), and in app/figma-plugin/src/ui.template.html (Lines 170-175), and the plugin copy already diverges (Missing vs Missing Info, different Risk wording). Please source this text from one shared constant next to SEVERITY_LABELS so every surface explains the severities the same way.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/report-html/index.ts` around lines 205 - 210, Create a single shared
constant for severity tooltips alongside SEVERITY_LABELS and replace the local
SEVERITY_TOOLTIPS with an import of that shared constant; ensure the shared
mapping uses the canonical keys and wording (e.g., "Missing Info" vs "Missing")
so all surfaces match, then update the other duplicated instances (the
HTML/template copies referenced) to consume the same shared constant or their
build-time equivalent so the texts are no longer duplicated and remain
consistent across SEVERITY_TOOLTIPS and SEVERITY_LABELS consumers.

@let-sunny let-sunny marked this pull request as draft March 25, 2026 23:48
- Remove leftover .cat-card highlight code (no longer rendered)
- Add document mouseleave to hide tooltip when pointer leaves plugin
- Replace whitespace-nowrap with max-w-[220px] for narrow viewport wrapping
  (applied to both web app and HTML report for consistency)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@let-sunny let-sunny marked this pull request as ready for review March 25, 2026 23:50
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/figma-plugin/src/ui.template.html`:
- Around line 170-176: Update the sevTips object and the tooltip text used when
building document.getElementById('summary-bar').innerHTML so wording and keys
match the report-html/web copy: change sevTips.blocking to "Cannot implement
correctly without fixing. Direct impact on screen reproduction.", sevTips.risk
to "Implementable now but will break or increase cost later.", and ensure the
missing tooltip text matches "Missing Info" (or the shared constant used by
report-html/web) rather than "missing"; adjust any references in the summary-bar
construction that use sevTips and the s.* counts so they use those exact shared
strings (look for the sevTips object and the code building 'summary-bar').

In `@app/web/src/index.html`:
- Around line 392-393: The severity tooltip in function renderDot still uses the
class "whitespace-nowrap" causing overflow on narrow viewports; update the
tooltip container (inside renderDot) to remove "whitespace-nowrap" and instead
apply wrapping constraints like "max-w-[220px] w-max" so the esc(tip) content
can wrap similarly to the category gauge tooltips; locate renderDot and SEV_TIPS
and replace the tooltip class accordingly.

In `@src/core/report-html/index.ts`:
- Line 218: The severity tooltip currently uses a hard NO_WRAP class
("whitespace-nowrap") causing overflow on narrow viewports; update the tooltip
wrapper that renders ${escapeHtml(tooltip)} to use the same bounded sizing
pattern as the category tooltip (replace "whitespace-nowrap" with "max-w-[220px]
w-max" alongside the existing classes) so long tooltip texts are constrained and
wrap correctly without overflowing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9888a3ae-3244-4889-97ae-dd84c19a9dac

📥 Commits

Reviewing files that changed from the base of the PR and between b46906a and 839ca1b.

📒 Files selected for processing (3)
  • app/figma-plugin/src/ui.template.html
  • app/web/src/index.html
  • src/core/report-html/index.ts

Comment on lines 170 to 176
var sevTips = { blocking: 'Cannot implement correctly without fixing.', risk: 'Implementable now but will break later.', missing: 'Information absent, AI must guess.', suggestion: 'Not problematic, but improves systemization.' };
document.getElementById('summary-bar').innerHTML =
'<div class="summary-item"><span class="dot dot-blocking"></span><span class="summary-count">' + s.blocking + '</span> Blocking</div>' +
'<div class="summary-item"><span class="dot dot-risk"></span><span class="summary-count">' + s.risk + '</span> Risk</div>' +
'<div class="summary-item"><span class="dot dot-missing"></span><span class="summary-count">' + s.missingInfo + '</span> Missing</div>' +
'<div class="summary-item"><span class="dot dot-suggestion"></span><span class="summary-count">' + s.suggestion + '</span> Suggestion</div>' +
'<div class="summary-item" data-tip="' + sevTips.blocking + '"><span class="dot dot-blocking"></span><span class="summary-count">' + s.blocking + '</span> Blocking</div>' +
'<div class="summary-item" data-tip="' + sevTips.risk + '"><span class="dot dot-risk"></span><span class="summary-count">' + s.risk + '</span> Risk</div>' +
'<div class="summary-item" data-tip="' + sevTips.missing + '"><span class="dot dot-missing"></span><span class="summary-count">' + s.missingInfo + '</span> Missing</div>' +
'<div class="summary-item" data-tip="' + sevTips.suggestion + '"><span class="dot dot-suggestion"></span><span class="summary-count">' + s.suggestion + '</span> Suggestion</div>' +
'<div class="summary-total">' + s.totalIssues + ' total</div>';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Severity tooltip wording diverges from other surfaces.

The sevTips object uses different keys and shorter text than the report-html and web versions:

Severity Plugin (here) Report/Web
Key missing "Missing Info"
Blocking "Cannot implement correctly without fixing." "Cannot implement correctly without fixing. Direct impact on screen reproduction."
Risk "Implementable now but will break later." "Implementable now but will break or increase cost later."

When consolidating to shared constants, ensure all surfaces use consistent wording.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/figma-plugin/src/ui.template.html` around lines 170 - 176, Update the
sevTips object and the tooltip text used when building
document.getElementById('summary-bar').innerHTML so wording and keys match the
report-html/web copy: change sevTips.blocking to "Cannot implement correctly
without fixing. Direct impact on screen reproduction.", sevTips.risk to
"Implementable now but will break or increase cost later.", and ensure the
missing tooltip text matches "Missing Info" (or the shared constant used by
report-html/web) rather than "missing"; adjust any references in the summary-bar
construction that use sevTips and the s.* counts so they use those exact shared
strings (look for the sevTips object and the code building 'summary-bar').

@let-sunny let-sunny marked this pull request as draft March 25, 2026 23:55
- Web app renderDot: whitespace-nowrap → max-w-[220px] w-max
- Report renderSummaryDot: same wrapping fix + arrow + hidden→block pattern
- Figma plugin sevTips: match exact wording from report/web constants

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@let-sunny let-sunny marked this pull request as ready for review March 25, 2026 23:56
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/figma-plugin/src/ui.template.html`:
- Line 162: The tooltip text from catTips is injected raw into the data-tip
attribute and can break HTML if it contains quotes; update the string building
where the '<div class="cat-label" ...>' is created to wrap the tooltip value
with the existing esc() function (i.e., use esc(catTips[cat] || '') ) before
concatenation so data-tip receives an escaped string, leaving CATEGORY_LABELS
and the element class name unchanged.
- Around line 172-175: The severity tooltip strings in the template are injected
directly into the data-tip attributes via sevTips.blocking / sevTips.risk /
sevTips.missing / sevTips.suggestion, which can allow attribute injection;
escape or HTML-encode these values before concatenation. Locate the snippet
building the summary-item divs (the lines containing '<div class="summary-item"
data-tip="' + sevTips.* + '">') and replace direct usage of sevTips.* with a
call to your existing HTML-escape utility (or implement one) so each severity
tip is encoded prior to being inserted into the data-tip attribute.

In `@app/web/src/index.html`:
- Around line 392-393: The renderDot tooltip is hover-only and lacks a fallback
title like the report renderer; update the renderDot function to set a title
attribute on the outer container (or the element that currently has class
"cursor-help") using the resolved tip text (var tip = SEV_TIPS[label] || '') and
fall back to the label when tip is empty (e.g., title=esc(tip || label)),
ensuring the existing hover tooltip remains unchanged.

In `@src/core/report-html/index.ts`:
- Around line 213-219: The tooltip is currently hover-only on a non-focusable
<div>, preventing keyboard users from revealing it; update the HTML returned
where `tooltip` is used (the template string around `SEVERITY_TOOLTIPS[label]`)
to make the container focusable (e.g., add tabindex="0" or use a <button>), add
aria-describedby on the container referencing a unique id for the tooltip, give
the tooltip <div> a role="tooltip" and that id, and change the visibility
trigger to include focus (e.g., use classes or attributes so the tooltip shows
on group-hover OR group-focus / :focus), ensuring you keep escaping via
`escapeHtml(tooltip)`.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: bd7196fa-0018-43a9-aab8-eb258da58e98

📥 Commits

Reviewing files that changed from the base of the PR and between 839ca1b and b15041b.

📒 Files selected for processing (3)
  • app/figma-plugin/src/ui.template.html
  • app/web/src/index.html
  • src/core/report-html/index.ts

Comment on lines +392 to +393
var SEV_TIPS = { Blocking: 'Cannot implement correctly without fixing. Direct impact on screen reproduction.', Risk: 'Implementable now but will break or increase cost later.', 'Missing Info': 'Information is absent, forcing AI to guess.', Suggestion: 'Not immediately problematic, but improves systemization.' };
function renderDot(cls, count, label) { var tip = SEV_TIPS[label] || ''; return '<div class="flex items-center gap-2 relative group cursor-help"><span class="w-2.5 h-2.5 rounded-full ' + cls + '"></span><span class="text-lg font-bold">' + count + '</span><span class="text-sm text-muted-foreground">' + label + '</span><div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 hidden group-hover:block bg-zinc-900 text-white text-xs px-3 py-2 rounded-md max-w-[220px] w-max z-10 shadow-lg pointer-events-none">' + esc(tip) + '<div class="absolute top-full left-1/2 -translate-x-1/2 border-4 border-transparent border-t-zinc-900"></div></div></div>'; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add a fallback tooltip title in renderDot for parity and resilience.

At Line 393, renderDot is hover-only and does not set a title, unlike the report renderer. Add the same fallback to keep behavior consistent.

🛠️ Suggested fix
-    function renderDot(cls, count, label) { var tip = SEV_TIPS[label] || ''; return '<div class="flex items-center gap-2 relative group cursor-help"><span class="w-2.5 h-2.5 rounded-full ' + cls + '"></span><span class="text-lg font-bold">' + count + '</span><span class="text-sm text-muted-foreground">' + label + '</span><div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 hidden group-hover:block bg-zinc-900 text-white text-xs px-3 py-2 rounded-md max-w-[220px] w-max z-10 shadow-lg pointer-events-none">' + esc(tip) + '<div class="absolute top-full left-1/2 -translate-x-1/2 border-4 border-transparent border-t-zinc-900"></div></div></div>'; }
+    function renderDot(cls, count, label) { var tip = SEV_TIPS[label] || ''; return '<div class="flex items-center gap-2 relative group cursor-help" title="' + esc(tip) + '"><span class="w-2.5 h-2.5 rounded-full ' + cls + '"></span><span class="text-lg font-bold">' + count + '</span><span class="text-sm text-muted-foreground">' + label + '</span><div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 hidden group-hover:block bg-zinc-900 text-white text-xs px-3 py-2 rounded-md max-w-[220px] w-max z-10 shadow-lg pointer-events-none">' + esc(tip) + '<div class="absolute top-full left-1/2 -translate-x-1/2 border-4 border-transparent border-t-zinc-900"></div></div></div>'; }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var SEV_TIPS = { Blocking: 'Cannot implement correctly without fixing. Direct impact on screen reproduction.', Risk: 'Implementable now but will break or increase cost later.', 'Missing Info': 'Information is absent, forcing AI to guess.', Suggestion: 'Not immediately problematic, but improves systemization.' };
function renderDot(cls, count, label) { var tip = SEV_TIPS[label] || ''; return '<div class="flex items-center gap-2 relative group cursor-help"><span class="w-2.5 h-2.5 rounded-full ' + cls + '"></span><span class="text-lg font-bold">' + count + '</span><span class="text-sm text-muted-foreground">' + label + '</span><div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 hidden group-hover:block bg-zinc-900 text-white text-xs px-3 py-2 rounded-md max-w-[220px] w-max z-10 shadow-lg pointer-events-none">' + esc(tip) + '<div class="absolute top-full left-1/2 -translate-x-1/2 border-4 border-transparent border-t-zinc-900"></div></div></div>'; }
var SEV_TIPS = { Blocking: 'Cannot implement correctly without fixing. Direct impact on screen reproduction.', Risk: 'Implementable now but will break or increase cost later.', 'Missing Info': 'Information is absent, forcing AI to guess.', Suggestion: 'Not immediately problematic, but improves systemization.' };
function renderDot(cls, count, label) { var tip = SEV_TIPS[label] || ''; return '<div class="flex items-center gap-2 relative group cursor-help" title="' + esc(tip) + '"><span class="w-2.5 h-2.5 rounded-full ' + cls + '"></span><span class="text-lg font-bold">' + count + '</span><span class="text-sm text-muted-foreground">' + label + '</span><div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 hidden group-hover:block bg-zinc-900 text-white text-xs px-3 py-2 rounded-md max-w-[220px] w-max z-10 shadow-lg pointer-events-none">' + esc(tip) + '<div class="absolute top-full left-1/2 -translate-x-1/2 border-4 border-transparent border-t-zinc-900"></div></div></div>'; }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/web/src/index.html` around lines 392 - 393, The renderDot tooltip is
hover-only and lacks a fallback title like the report renderer; update the
renderDot function to set a title attribute on the outer container (or the
element that currently has class "cursor-help") using the resolved tip text (var
tip = SEV_TIPS[label] || '') and fall back to the label when tip is empty (e.g.,
title=esc(tip || label)), ensuring the existing hover tooltip remains unchanged.

Comment on lines +213 to 219
const tooltip = SEVERITY_TOOLTIPS[label] ?? "";
return `<div class="flex items-center gap-2 relative group cursor-help" title="${escapeHtml(tooltip)}">
<span class="w-2.5 h-2.5 rounded-full ${dotClass}"></span>
<span class="text-lg font-bold tracking-tight">${count}</span>
<span class="text-sm text-muted-foreground">${label}</span>
<div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 hidden group-hover:block bg-zinc-900 text-white text-xs px-3 py-2 rounded-md max-w-[220px] w-max z-10 shadow-lg pointer-events-none">${escapeHtml(tooltip)}<div class="absolute top-full left-1/2 -translate-x-1/2 border-4 border-transparent border-t-zinc-900"></div></div>
</div>`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Make severity tooltip reachable by keyboard users.

At Line 214 and Line 218, the tooltip is hover-only on a non-focusable <div>, so keyboard users cannot reveal it.

♿ Suggested accessibility fix
-  return `<div class="flex items-center gap-2 relative group cursor-help" title="${escapeHtml(tooltip)}">
+  return `<div class="flex items-center gap-2 relative group cursor-help" title="${escapeHtml(tooltip)}" tabindex="0">
           <span class="w-2.5 h-2.5 rounded-full ${dotClass}"></span>
           <span class="text-lg font-bold tracking-tight">${count}</span>
           <span class="text-sm text-muted-foreground">${label}</span>
-          <div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 hidden group-hover:block bg-zinc-900 text-white text-xs px-3 py-2 rounded-md max-w-[220px] w-max z-10 shadow-lg pointer-events-none">${escapeHtml(tooltip)}<div class="absolute top-full left-1/2 -translate-x-1/2 border-4 border-transparent border-t-zinc-900"></div></div>
+          <div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 bg-zinc-900 text-white text-xs px-3 py-2 rounded-md max-w-[220px] w-max z-10 shadow-lg pointer-events-none opacity-0 group-hover:opacity-100 group-focus:opacity-100 transition-opacity">${escapeHtml(tooltip)}<div class="absolute top-full left-1/2 -translate-x-1/2 border-4 border-transparent border-t-zinc-900"></div></div>
         </div>`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/report-html/index.ts` around lines 213 - 219, The tooltip is
currently hover-only on a non-focusable <div>, preventing keyboard users from
revealing it; update the HTML returned where `tooltip` is used (the template
string around `SEVERITY_TOOLTIPS[label]`) to make the container focusable (e.g.,
add tabindex="0" or use a <button>), add aria-describedby on the container
referencing a unique id for the tooltip, give the tooltip <div> a role="tooltip"
and that id, and change the visibility trigger to include focus (e.g., use
classes or attributes so the tooltip shows on group-hover OR group-focus /
:focus), ensuring you keep escaping via `escapeHtml(tooltip)`.

Prevent potential attribute injection by escaping tooltip text
with esc() before inserting into data-tip attributes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
app/figma-plugin/src/ui.template.html (2)

155-156: 🛠️ Refactor suggestion | 🟠 Major

Lift the tooltip copy into shared constants.

catTips and sevTips are now plugin-local sources of truth even though this file already consumes injected shared UI maps. That drift is already visible here: the summary bar hardcodes Missing while the issue sections render severity labels from SEVERITY_LABELS. Please move the tooltip and label config into app/shared/ and read it from the injected constants. As per coding guidelines, app/**: Browser runtime code. Shared UI components are in app/shared/. Both inline shared UI from app/shared at build time.

Also applies to: 170-175

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/figma-plugin/src/ui.template.html` around lines 155 - 156, Remove the
local tooltip/label maps (catTips and sevTips) from the UI template and instead
read the corresponding injected shared constants from app/shared (the same maps
used elsewhere like SEVERITY_LABELS); replace references to catTips and sevTips
with the injected shared names (use the existing injected UI maps that the
template already consumes) so the summary bar and issue sections use the single
source of truth; ensure you no longer hardcode values in the template and that
references (e.g., where catTips was used for the 'cat-grid' rendering and where
sevTips/SEVERITY_LABELS are used for severity labels) point to the injected
constants.

376-379: ⚠️ Potential issue | 🟡 Minor

Hide on iframe exit from the mouseout path too.

Line 378 still decides from e.target, so exiting the iframe directly from a [data-tip] node skips hide(). MDN documents mouseleave as an element event that does not bubble, and relatedTarget is null when there is no secondary target, so the reliable exit signal here is mouseout plus e.relatedTarget. (developer.mozilla.org)

🐛 Suggested fix
-      document.addEventListener('mouseleave', hide);
       document.addEventListener('mouseout', function(e) {
-        if (!e.target.closest('[data-tip]')) hide();
+        var to = e.relatedTarget && e.relatedTarget.closest ? e.relatedTarget.closest('[data-tip]') : null;
+        if (!to) hide();
       });
+      document.documentElement.addEventListener('mouseleave', hide);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/figma-plugin/src/ui.template.html` around lines 376 - 379, The mouseout
handler on document currently checks only e.target and misses cases where the
pointer leaves the page/iframe (relatedTarget is null), so update the
document.addEventListener('mouseout', ...) listener to also call hide() when
e.relatedTarget is null (pointer left the iframe/window) or when the
relatedTarget is not inside a [data-tip] element; reference the existing hide
function and the document.addEventListener('mouseout', function(e) { ... })
handler to implement this additional relatedTarget check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@app/figma-plugin/src/ui.template.html`:
- Around line 155-156: Remove the local tooltip/label maps (catTips and sevTips)
from the UI template and instead read the corresponding injected shared
constants from app/shared (the same maps used elsewhere like SEVERITY_LABELS);
replace references to catTips and sevTips with the injected shared names (use
the existing injected UI maps that the template already consumes) so the summary
bar and issue sections use the single source of truth; ensure you no longer
hardcode values in the template and that references (e.g., where catTips was
used for the 'cat-grid' rendering and where sevTips/SEVERITY_LABELS are used for
severity labels) point to the injected constants.
- Around line 376-379: The mouseout handler on document currently checks only
e.target and misses cases where the pointer leaves the page/iframe
(relatedTarget is null), so update the document.addEventListener('mouseout',
...) listener to also call hide() when e.relatedTarget is null (pointer left the
iframe/window) or when the relatedTarget is not inside a [data-tip] element;
reference the existing hide function and the
document.addEventListener('mouseout', function(e) { ... }) handler to implement
this additional relatedTarget check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3785048b-0b4a-4f29-94de-09b036539129

📥 Commits

Reviewing files that changed from the base of the PR and between b15041b and 1f9818a.

📒 Files selected for processing (1)
  • app/figma-plugin/src/ui.template.html

@let-sunny let-sunny merged commit cc39c7b into main Mar 26, 2026
3 checks passed
@let-sunny let-sunny deleted the feat/severity-tooltips branch March 26, 2026 00:12
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