Skip to content

fix(billing): redesign subscription cards and polish billing page UI#492

Merged
graycyrus merged 3 commits intotinyhumansai:mainfrom
graycyrus:fix/billing-page-ui-polish
Apr 10, 2026
Merged

fix(billing): redesign subscription cards and polish billing page UI#492
graycyrus merged 3 commits intotinyhumansai:mainfrom
graycyrus:fix/billing-page-ui-polish

Conversation

@graycyrus
Copy link
Copy Markdown
Contributor

@graycyrus graycyrus commented Apr 10, 2026

Summary

  • Redesign subscription plan cards with clear visual hierarchy: plan name/tagline left, prominent price right, vertical feature checklist with check/X icons, full-width CTA buttons
  • Add "Popular" badge to Basic plan with accent border and shadow for visual emphasis
  • Remove confusing technical pills (monthly budget, 7-day cycle, 10-hour cap, "Discount: 0%")
  • Fix double padding throughout the billing page (SubscriptionPlans and AutoRechargeSection had internal padding on top of parent's p-4)
  • Fix "5-hour cap" label to "10-hour cap" and hide cap row when both values are zero
  • Fix progress bar background from dark stone-700/60 to light stone-200
  • Shorten verbose copy across Current Plan header, divider, and Pay as You Go description
  • Remove redundant "Why upgrade?" section (now communicated by improved plan cards)

Changed files

  • billingHelpers.ts — Added recommended and tagline fields to PlanMeta, rewrote feature text
  • SubscriptionPlans.tsx — New card layout, removed doubled padding
  • BillingPanel.tsx — Tightened copy, removed 0% discount pill and "Why upgrade?" section
  • InferenceBudget.tsx — Fixed label, hid zero-value cap, fixed progress bar bg
  • AutoRechargeSection.tsx — Removed extra padding wrapper
  • PayAsYouGoCard.tsx — Shortened explanation text

Test plan

  • yarn typecheck passes
  • yarn test:unit -- billingHelpers — all 35 tests pass
  • Visual check: open Settings > Billing, verify consistent padding, clean cards, no noise pills

Summary by CodeRabbit

  • New Features
    • Added guided setup flows for Voice, Screen Intelligence, and Autocomplete with step-by-step configuration
    • Implemented real-time skill status indicators displaying connection states and operational availability
    • Redesigned subscription plan cards with recommended plan badges and structured feature comparisons
    • Clarified billing messaging with simplified credit descriptions and improved plan layout

Replace static "Settings" buttons on Screen Intelligence, Text Auto-Complete,
and Voice Intelligence skill cards with live status dots, labels, and dynamic
CTA buttons (Enable/Setup/Manage/Retry) matching third-party skill UX.

Each built-in skill gets:
- A status hook deriving card state from core RPC snapshots
- A setup/enable modal with step-by-step flows (permissions, enable, success)
- Escape key + aria dialog attributes for accessibility

Screen Intelligence: permission grant flow → enable → success
Text Auto-Complete: one-click enable → success
Voice Intelligence: STT model check → enable voice server → success
…e UI

- Redesign plan cards with clear visual hierarchy: name/tagline left, prominent price right,
  vertical feature checklist with check/X icons, full-width CTA buttons
- Add "Popular" badge to Basic plan with accent border and shadow
- Add taglines and rewrite features to user-friendly language
- Remove confusing technical pills (monthly budget, 7-day cycle, 10-hour cap, discount %)
- Hide "Premium-usage discount: 0%" pill for free users
- Remove redundant "Why upgrade?" section
- Fix double padding (SubscriptionPlans px-4/mx-4 and AutoRecharge px-4 inside parent p-4)
- Fix "5-hour cap" label to "10-hour cap" and hide when both values are zero
- Fix progress bar background from dark stone-700/60 to light stone-200
- Shorten verbose copy across Current Plan header, divider, and Pay as You Go description
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 10, 2026

📝 Walkthrough

Walkthrough

This PR updates billing UI with simplified messaging and layout restructuring, introduces three new skill setup modal components for voice, screen intelligence, and autocomplete, and adds corresponding status hooks that derive normalized UI state from core runtime snapshots. The Skills page integrates these new modals and status hooks to manage setup flows for built-in skills.

Changes

Cohort / File(s) Summary
Billing UI — Copy & Layout Updates
app/src/components/settings/panels/BillingPanel.tsx, app/src/components/settings/panels/billing/InferenceBudget.tsx, app/src/components/settings/panels/billing/PayAsYouGoCard.tsx
Simplified free-plan messaging to reference "pay-as-you-go credits," adjusted overage/top-up descriptions, removed "Why upgrade?" info block from BillingPanel, conditionally rendered discount badge, updated inference budget progress bar colors (bg-stone-700/60bg-stone-200), made 10-hour cap display conditional, and updated pay-as-you-go credit consumption text.
Billing Layout & Styling
app/src/components/settings/panels/billing/AutoRechargeSection.tsx, app/src/components/settings/panels/billing/SubscriptionPlans.tsx
Removed outer padding wrapper and restructured JSX nesting in AutoRechargeSection; simplified SubscriptionPlans import, removed px-4 padding, added conditional "Popular" badge and tagline rendering, reworked header/feature layouts with structured lists, replaced CTA rendering logic, and removed budget/cap pills.
Billing Data Model
app/src/components/settings/panels/billingHelpers.ts
Extended PlanMeta interface with optional recommended?: boolean and tagline?: string fields; updated PLANS data to include taglines, adjusted feature descriptions for FREE/BASIC/PRO tiers.
Skill Setup Modals — New Components
app/src/components/skills/AutocompleteSetupModal.tsx, app/src/components/skills/ScreenIntelligenceSetupModal.tsx, app/src/components/skills/VoiceSetupModal.tsx
Added three new portal-based modal components for skill setup flows: AutocompleteSetupModal (enable → success), ScreenIntelligenceSetupModal (permissions → enable → success with platform/permission checks), and VoiceSetupModal (setup/enable → success with STT model detection).
Skill Status Hooks — New Hooks
app/src/features/autocomplete/useAutocompleteSkillStatus.ts, app/src/features/screen-intelligence/useScreenIntelligenceSkillStatus.ts, app/src/features/voice/useVoiceSkillStatus.ts
Added three new React hooks that derive normalized skill UI status objects from core runtime state, handling offline/unsupported/error/enabled/active states with corresponding CTA labels/variants and platform/permission flags; voice hook includes periodic server status polling on 3-second interval.
Skills Page Integration
app/src/pages/Skills.tsx
Imported and integrated three new status hooks and setup modals; added modal state management; replaced generic skill card CTAs for screen-intelligence, text-autocomplete, and voice-stt with state-aware rendering and conditional modal/routing behavior; conditionally rendered setup modals with onClose handlers and initialStep props.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Modal as ScreenIntelligence<br/>SetupModal
    participant Core as Core API
    participant State as Core State
    
    User->>Modal: Open modal
    
    rect rgba(100, 150, 200, 0.5)
    Note over Modal: Permissions Step
    User->>Modal: Request permission
    Modal->>Core: requestPermission(screen_recording)
    Core-->>Modal: Permission updated
    Modal->>Modal: Check if all permissions granted
    end
    
    rect rgba(150, 150, 100, 0.5)
    Note over Modal: Enable Step (auto-advance on all permissions)
    User->>Modal: Click "Enable Screen Intelligence"
    Modal->>Core: openhumanUpdateScreenIntelligenceSettings({enabled: true})
    Core-->>Modal: Success
    Modal->>Core: refreshStatus()
    Core-->>State: Updated runtime state
    Modal->>Modal: Transition to success
    end
    
    rect rgba(150, 200, 100, 0.5)
    Note over Modal: Success Step
    Modal-->>User: Show confirmation
    User->>Modal: Close or navigate to settings
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • senamakel

🐰 Soft buttons bloom where once were hard,
Skills arrange their dances, guarded, starred.
Billing's tongue grows gentle, bills grow wise,
Three new modals open to the skies. ✨
Setup flows with rabbit-hopping grace!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 accurately describes the main changes: redesign of subscription plan cards and polish of the overall billing page UI, which aligns with the primary focus of the changeset.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

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

🧹 Nitpick comments (2)
app/src/components/settings/panels/billing/InferenceBudget.tsx (1)

42-45: Consider aliasing legacy cycleLimit5hr to a 10-hour semantic local name.

Line 44 shows “10-hour cap” while reading cycleLimit5hr; a local alias would reduce maintenance confusion.

♻️ Optional clarity refactor
-            {((teamUsage.cycleLimit5hr ?? 0) > 0 || (teamUsage.fiveHourCapUsd ?? 0) > 0) && (
+            {(() => {
+              const cycleLimit10hrUsd = teamUsage.cycleLimit5hr ?? 0;
+              const tenHourCapUsd = teamUsage.fiveHourCapUsd ?? 0;
+              return (cycleLimit10hrUsd > 0 || tenHourCapUsd > 0) && (
               <span className="text-[11px] text-stone-500">
-                10-hour cap: ${(teamUsage.cycleLimit5hr ?? 0).toFixed(2)} / $
-                {(teamUsage.fiveHourCapUsd ?? 0).toFixed(2)}
+                10-hour cap: ${cycleLimit10hrUsd.toFixed(2)} / ${tenHourCapUsd.toFixed(2)}
               </span>
-            )}
+              );
+            })()}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/settings/panels/billing/InferenceBudget.tsx` around lines
42 - 45, The label "10-hour cap" uses the legacy field teamUsage.cycleLimit5hr
which creates semantic confusion; introduce a local alias (e.g., tenHourCap or
tenHourCycleLimit) at the top of the InferenceBudget component that maps to
teamUsage.cycleLimit5hr and use that alias in the JSX and any related
calculations so the name matches the displayed "10-hour" semantics and improves
maintainability.
app/src/components/skills/ScreenIntelligenceSetupModal.tsx (1)

116-120: Consider deriving step from permissions state instead of useEffect transition.

This useEffect calls setStep when allGranted changes, which could be flagged by react-hooks/set-state-in-effect. While this auto-advance pattern is common, you could alternatively derive the effective step:

const effectiveStep = step === 'permissions' && allGranted ? 'enable' : step;

However, if this pattern passes your lint rules and works correctly for the modal flow, it's acceptable since it represents a deliberate state machine transition rather than the problematic "reset state at effect start" anti-pattern.

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

In `@app/src/components/skills/ScreenIntelligenceSetupModal.tsx` around lines 116
- 120, The useEffect that advances step from 'permissions' to 'enable' by
calling setStep when allGranted changes can be replaced by deriving the modal's
displayed step to avoid state updates inside effects; instead compute an
effectiveStep (e.g., using step, allGranted and the 'permissions' string) and
use that for rendering and control flow, removing the useEffect and references
to setStep in this transition path (keep setStep only for explicit user-driven
changes); update any places reading step to use effectiveStep so the modal
auto-advances without calling setStep inside the useEffect.
🤖 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/src/components/settings/panels/billing/AutoRechargeSection.tsx`:
- Around line 161-165: The weekly usage text in AutoRechargeSection uses
arSettings.spentThisWeekUsd.toFixed(2) but prints arSettings.weeklyLimitUsd raw,
causing inconsistent currency precision; update the JSX that renders the span in
AutoRechargeSection (where arSettings.spentThisWeekUsd and
arSettings.weeklyLimitUsd are referenced) to format both values consistently
(e.g., call .toFixed(2) or use a shared formatCurrency utility) so the spent and
weekly limit show the same decimal precision and consistent currency
presentation.
- Around line 86-100: The toggle and other action buttons in
AutoRechargeSection.tsx are missing explicit types (defaulting to submit) which
can accidentally submit surrounding forms; update every <button> element in this
file to include type="button" — start with the toggle button that uses
onArToggle and references arSaving/arSettings and then add the same attribute to
all other action buttons in this component so none will act as form submitters.

In `@app/src/components/settings/panels/billing/SubscriptionPlans.tsx`:
- Around line 109-135: The two decorative SVGs rendered inside the
SubscriptionPlans component (the check icon and the X icon in the f.included
conditional) should be hidden from assistive tech; add aria-hidden="true" to
both <svg> elements (the ones rendering the check path "M5 13l4 4L19 7" and the
X path "M6 18L18 6M6 6l12 12") so screen readers ignore these decorative icons
while preserving their visual appearance.

---

Nitpick comments:
In `@app/src/components/settings/panels/billing/InferenceBudget.tsx`:
- Around line 42-45: The label "10-hour cap" uses the legacy field
teamUsage.cycleLimit5hr which creates semantic confusion; introduce a local
alias (e.g., tenHourCap or tenHourCycleLimit) at the top of the InferenceBudget
component that maps to teamUsage.cycleLimit5hr and use that alias in the JSX and
any related calculations so the name matches the displayed "10-hour" semantics
and improves maintainability.

In `@app/src/components/skills/ScreenIntelligenceSetupModal.tsx`:
- Around line 116-120: The useEffect that advances step from 'permissions' to
'enable' by calling setStep when allGranted changes can be replaced by deriving
the modal's displayed step to avoid state updates inside effects; instead
compute an effectiveStep (e.g., using step, allGranted and the 'permissions'
string) and use that for rendering and control flow, removing the useEffect and
references to setStep in this transition path (keep setStep only for explicit
user-driven changes); update any places reading step to use effectiveStep so the
modal auto-advances without calling setStep inside the useEffect.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cfa14a9e-d325-4f6f-8536-811390b18b5a

📥 Commits

Reviewing files that changed from the base of the PR and between aee9c52 and 6cb615e.

📒 Files selected for processing (13)
  • app/src/components/settings/panels/BillingPanel.tsx
  • app/src/components/settings/panels/billing/AutoRechargeSection.tsx
  • app/src/components/settings/panels/billing/InferenceBudget.tsx
  • app/src/components/settings/panels/billing/PayAsYouGoCard.tsx
  • app/src/components/settings/panels/billing/SubscriptionPlans.tsx
  • app/src/components/settings/panels/billingHelpers.ts
  • app/src/components/skills/AutocompleteSetupModal.tsx
  • app/src/components/skills/ScreenIntelligenceSetupModal.tsx
  • app/src/components/skills/VoiceSetupModal.tsx
  • app/src/features/autocomplete/useAutocompleteSkillStatus.ts
  • app/src/features/screen-intelligence/useScreenIntelligenceSkillStatus.ts
  • app/src/features/voice/useVoiceSkillStatus.ts
  • app/src/pages/Skills.tsx

Comment on lines +86 to +100
<button
onClick={onArToggle}
disabled={arSaving}
role="switch"
aria-checked={arSettings?.enabled ?? false}
aria-label="Toggle auto-recharge"
className={`relative w-10 h-5 rounded-full transition-colors focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 focus-visible:ring-offset-stone-900 ${
arSaving ? 'opacity-50 cursor-not-allowed' : ''
} ${arSettings?.enabled ? 'bg-primary-500' : 'bg-stone-600'}`}>
<span
className={`absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white shadow transition-transform ${
arSettings?.enabled ? 'translate-x-5' : 'translate-x-0'
}`}
/>
</button>
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

python - <<'PY'
import re
from pathlib import Path

for path in Path(".").rglob("*.tsx"):
    text = path.read_text(encoding="utf-8", errors="ignore")
    for m in re.finditer(r"<button\b([^>]*)>", text, flags=re.S):
        attrs = m.group(1)
        if re.search(r"\btype\s*=", attrs) is None:
            line = text.count("\n", 0, m.start()) + 1
            print(f"{path}:{line}: <button> missing explicit type")
PY

Repository: tinyhumansai/openhuman

Length of output: 14627


Add explicit type="button" on all action buttons.

Several buttons currently lack explicit type, which defaults to type="submit". If this section is nested in a form, clicks can unintentionally trigger form submission.

Buttons missing type in this file: lines 86, 120, 204, 223, 242, 265, 283, 360, 370, 376, 383.

Example fix
-<button
+<button type="button"
   onClick={onArToggle}
   disabled={arSaving}
   role="switch"
   aria-checked={arSettings?.enabled ?? false}
   aria-label="Toggle auto-recharge"

Apply the same pattern to all other buttons in the file.

📝 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
<button
onClick={onArToggle}
disabled={arSaving}
role="switch"
aria-checked={arSettings?.enabled ?? false}
aria-label="Toggle auto-recharge"
className={`relative w-10 h-5 rounded-full transition-colors focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 focus-visible:ring-offset-stone-900 ${
arSaving ? 'opacity-50 cursor-not-allowed' : ''
} ${arSettings?.enabled ? 'bg-primary-500' : 'bg-stone-600'}`}>
<span
className={`absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white shadow transition-transform ${
arSettings?.enabled ? 'translate-x-5' : 'translate-x-0'
}`}
/>
</button>
<button
type="button"
onClick={onArToggle}
disabled={arSaving}
role="switch"
aria-checked={arSettings?.enabled ?? false}
aria-label="Toggle auto-recharge"
className={`relative w-10 h-5 rounded-full transition-colors focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 focus-visible:ring-offset-stone-900 ${
arSaving ? 'opacity-50 cursor-not-allowed' : ''
} ${arSettings?.enabled ? 'bg-primary-500' : 'bg-stone-600'}`}>
<span
className={`absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white shadow transition-transform ${
arSettings?.enabled ? 'translate-x-5' : 'translate-x-0'
}`}
/>
</button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/settings/panels/billing/AutoRechargeSection.tsx` around
lines 86 - 100, The toggle and other action buttons in AutoRechargeSection.tsx
are missing explicit types (defaulting to submit) which can accidentally submit
surrounding forms; update every <button> element in this file to include
type="button" — start with the toggle button that uses onArToggle and references
arSaving/arSettings and then add the same attribute to all other action buttons
in this component so none will act as form submitters.

Comment on lines +161 to +165
{arSettings.spentThisWeekUsd > 0 && (
<span className="text-[10px] text-stone-400">
${arSettings.spentThisWeekUsd.toFixed(2)} of ${arSettings.weeklyLimitUsd} used this
week
</span>
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

Use consistent currency formatting in weekly usage copy.

Line 163 formats the spent amount to 2 decimals but not the weekly limit, which can render inconsistent money precision.

Suggested patch
- ${arSettings.spentThisWeekUsd.toFixed(2)} of ${arSettings.weeklyLimitUsd} used this
+ ${arSettings.spentThisWeekUsd.toFixed(2)} of ${arSettings.weeklyLimitUsd.toFixed(2)} used this
  week
📝 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
{arSettings.spentThisWeekUsd > 0 && (
<span className="text-[10px] text-stone-400">
${arSettings.spentThisWeekUsd.toFixed(2)} of ${arSettings.weeklyLimitUsd} used this
week
</span>
{arSettings.spentThisWeekUsd > 0 && (
<span className="text-[10px] text-stone-400">
${arSettings.spentThisWeekUsd.toFixed(2)} of ${arSettings.weeklyLimitUsd.toFixed(2)} used this
week
</span>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/settings/panels/billing/AutoRechargeSection.tsx` around
lines 161 - 165, The weekly usage text in AutoRechargeSection uses
arSettings.spentThisWeekUsd.toFixed(2) but prints arSettings.weeklyLimitUsd raw,
causing inconsistent currency precision; update the JSX that renders the span in
AutoRechargeSection (where arSettings.spentThisWeekUsd and
arSettings.weeklyLimitUsd are referenced) to format both values consistently
(e.g., call .toFixed(2) or use a shared formatCurrency utility) so the spent and
weekly limit show the same decimal precision and consistent currency
presentation.

Comment on lines +109 to +135
{f.included ? (
<svg
className="w-4 h-4 text-sage-500 flex-shrink-0 mt-0.5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 13l4 4L19 7"
/>
</svg>
) : (
<svg
className="w-4 h-4 text-stone-300 flex-shrink-0 mt-0.5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
)}
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 decorative checklist icons from assistive tech.

Line 110 and Line 123 SVGs are purely decorative; mark them aria-hidden to avoid noisy screen-reader output.

♿ Suggested fix
-                    <svg
+                    <svg
+                      aria-hidden="true"
+                      focusable="false"
                       className="w-4 h-4 text-sage-500 flex-shrink-0 mt-0.5"
                       fill="none"
                       stroke="currentColor"
                       viewBox="0 0 24 24">
@@
-                    <svg
+                    <svg
+                      aria-hidden="true"
+                      focusable="false"
                       className="w-4 h-4 text-stone-300 flex-shrink-0 mt-0.5"
                       fill="none"
                       stroke="currentColor"
                       viewBox="0 0 24 24">
📝 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
{f.included ? (
<svg
className="w-4 h-4 text-sage-500 flex-shrink-0 mt-0.5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 13l4 4L19 7"
/>
</svg>
) : (
<svg
className="w-4 h-4 text-stone-300 flex-shrink-0 mt-0.5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
)}
{f.included ? (
<svg
aria-hidden="true"
focusable="false"
className="w-4 h-4 text-sage-500 flex-shrink-0 mt-0.5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 13l4 4L19 7"
/>
</svg>
) : (
<svg
aria-hidden="true"
focusable="false"
className="w-4 h-4 text-stone-300 flex-shrink-0 mt-0.5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/settings/panels/billing/SubscriptionPlans.tsx` around
lines 109 - 135, The two decorative SVGs rendered inside the SubscriptionPlans
component (the check icon and the X icon in the f.included conditional) should
be hidden from assistive tech; add aria-hidden="true" to both <svg> elements
(the ones rendering the check path "M5 13l4 4L19 7" and the X path "M6 18L18 6M6
6l12 12") so screen readers ignore these decorative icons while preserving their
visual appearance.

@graycyrus graycyrus merged commit 3a2e026 into tinyhumansai:main Apr 10, 2026
7 of 9 checks passed
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