Skip to content

feat(rate-limit): update quota banner from response headers and persi…#80

Open
Ri1tik wants to merge 1 commit into
AOSSIE-Org:mainfrom
Ri1tik:improvement/rate-limit-live-updates
Open

feat(rate-limit): update quota banner from response headers and persi…#80
Ri1tik wants to merge 1 commit into
AOSSIE-Org:mainfrom
Ri1tik:improvement/rate-limit-live-updates

Conversation

@Ri1tik

@Ri1tik Ri1tik commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Addressed Issues:

Fixes #(issue number)

Screenshots/Recordings:

Before
https://drive.google.com/file/d/1q-_7wWq25cndkLD_qype1LJuOBM2Rr1i/view?usp=drive_link
image

After
https://drive.google.com/file/d/13mKHtOhIFFp896vYoL8PKb-rhzp9FVnT/view?usp=drive_link
image

Additional Notes:

This PR improves GitHub API rate limit monitoring by:

  • Updating rate limit information from response headers on every API request.
  • Persisting the latest rate limit state in localStorage.
  • Restoring rate limit information after page refreshes.
  • Automatically invalidating stale rate limit data after the GitHub reset time.
  • Eliminating the need for dedicated /rate_limit API calls, reducing unnecessary API consumption.

Benefits

  • More accurate real-time rate limit display.
  • Zero additional API requests for rate limit tracking.
  • Better user awareness of remaining GitHub API quota.
  • Reduced risk of hitting rate limits during exploration and governance audits.

Checklist

  • My code follows the project's code style and conventions
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings or errors
  • I have joined the Discord server and I will share a link to this PR with the project maintainers there
  • I have read the Contributing Guidelines

⚠️ AI Notice - Important!

We encourage contributors to use AI tools responsibly when creating Pull Requests. While AI can be a valuable aid, it is essential to ensure that your contributions meet the task requirements, build successfully, include relevant tests, and pass all linters. Submissions that do not meet these standards may be closed without warning to maintain the quality and integrity of the project. Please take the time to understand the changes you are proposing and their impact.

Summary by CodeRabbit

  • Bug Fixes
    • Rate limit banner now displays used quota and reset time dynamically
    • Rate limit data persists and restores across sessions
    • Improved rate limit update synchronization across the application

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Rate limit data propagation is refactored to an event-driven model: fetchWithCache in github.js dispatches a rate-limit-update CustomEvent with x-ratelimit-* header values after each GitHub API call. AppContext listens for this event, persists rate limit data to localStorage with expiry validation, and schedules automatic clearance. RateLimitBanner now renders a dynamic used count and formatted reset time.

Changes

Event-Driven Rate Limit Tracking

Layer / File(s) Summary
fetchWithCache CustomEvent dispatch and cache error handling
src/services/github.js
fetchWithCache dispatches a rate-limit-update CustomEvent on window after each GitHub fetch, populating detail with x-ratelimit-limit, x-ratelimit-remaining, x-ratelimit-used, and x-ratelimit-reset header values. cacheSet and cacheClear gain explicit tx.onerror handlers resolving false. Remaining changes are formatting only.
AppContext rate limit state via event listener and localStorage
src/context/AppContext.jsx
Adds getStoredRateLimit() to load and validate cached rate limit data from localStorage. Initial rateLimit state is seeded from that cache. Two useEffect hooks are added: one subscribes to rate-limit-update events to update state and persist to localStorage, the other schedules a timeout to clear state and storage at the reset time. The direct fetchRateLimit(pat) call after explore is removed.
RateLimitBanner dynamic used/reset display
src/components/RateLimitBanner.jsx
Replaces the static "resets hourly" label with a dynamic line showing rateLimit.used and a localized reset time computed from rateLimit.reset (seconds converted to milliseconds, formatted via toLocaleTimeString()).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

Typescript Lang

Poem

🐇 Hoppity-hop through the API lane,
Rate limits tracked without extra pain!
Events dispatched on the window wide,
localStorage keeps the reset inside.
The banner now shows what's used and when—
No more static text, it's dynamic zen! ✨

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: updating the rate-limit banner to use response headers and persist data, which aligns with the core functionality across all three modified files.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ 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 and usage tips.

@github-actions github-actions Bot added no-issue-linked PR has no linked issue frontend Frontend changes javascript JavaScript/TypeScript changes size/M 51-200 lines changed external-contributor External contributor and removed size/M 51-200 lines changed labels Jun 19, 2026

@coderabbitai coderabbitai 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.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/RateLimitBanner.jsx`:
- Around line 36-39: The span element in RateLimitBanner directly accesses
rateLimit.used and rateLimit.reset without checking if these properties exist,
which could display "undefined" or create invalid dates if the event payload is
incomplete. Add fallback values for both rateLimit.used (such as a default value
like 0) and rateLimit.reset (such as a default value like 0) using optional
chaining operators or logical OR operators to ensure valid display content even
when these properties are undefined.
- Around line 36-39: The toLocaleTimeString() method call on the rateLimit.reset
date in the RateLimitBanner component displays only the time without timezone
information, which can confuse users about when the rate limit actually resets.
Modify the toLocaleTimeString() call to include timezone information by passing
an options object as a second parameter that specifies timeZoneName property, so
users see both the local time and their timezone abbreviation (e.g., "4:07:57 PM
PST") for better clarity across different user timezones.

In `@src/context/AppContext.jsx`:
- Around line 38-49: The handler function in the useEffect for the
'rate-limit-update' event listener directly uses e.detail without validation
before calling setRateLimit and storing it in localStorage with the key
'oe_rate_limit'. Add validation logic to verify that e.detail is a valid object
with the expected structure and properties (such as checking that it is an
object type and has required fields) before proceeding with setRateLimit and
localStorage.setItem. Only allow the state and localStorage updates to occur if
the validation passes.

In `@src/services/github.js`:
- Around line 65-74: Add null checks before converting rate limit header values
to numbers in the custom event dispatch. For each header retrieval
(x-ratelimit-limit, x-ratelimit-remaining, x-ratelimit-used, x-ratelimit-reset),
verify that the header exists before passing it to Number(). Either check if the
header value is null before conversion or provide sensible default values when
headers are missing. This prevents Number(null) from returning 0 and ensures
rate limit information displays correctly even when headers are absent.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 90bb8d9d-3bbf-4e42-8d76-54077ac92a83

📥 Commits

Reviewing files that changed from the base of the PR and between 46c5c95 and 06b9f30.

📒 Files selected for processing (3)
  • src/components/RateLimitBanner.jsx
  • src/context/AppContext.jsx
  • src/services/github.js

Comment on lines +36 to +39
<span style={{ fontSize: 11, color: 'var(--text2)' }}>
Used: {rateLimit.used} • Reset at{' '}
{new Date(rateLimit.reset * 1000).toLocaleTimeString()}
</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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add fallback for potentially undefined rate limit properties.

If the rate-limit-update event payload is incomplete (due to the missing validation in AppContext), rateLimit.used or rateLimit.reset could be undefined, displaying "Used: undefined" or an invalid date.

🛡️ Proposed fix with fallbacks
 <span style={{ fontSize: 11, color: 'var(--text2)' }}>
-  Used: {rateLimit.used} • Reset at{' '}
-  {new Date(rateLimit.reset * 1000).toLocaleTimeString()}
+  Used: {rateLimit.used ?? 0} • Reset at{' '}
+  {rateLimit.reset ? new Date(rateLimit.reset * 1000).toLocaleTimeString() : 'Unknown'}
 </span>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/RateLimitBanner.jsx` around lines 36 - 39, The span element in
RateLimitBanner directly accesses rateLimit.used and rateLimit.reset without
checking if these properties exist, which could display "undefined" or create
invalid dates if the event payload is incomplete. Add fallback values for both
rateLimit.used (such as a default value like 0) and rateLimit.reset (such as a
default value like 0) using optional chaining operators or logical OR operators
to ensure valid display content even when these properties are undefined.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider adding timezone and locale control for better internationalization.

The toLocaleTimeString() call uses default browser locale and no timezone information is displayed. Users in different timezones may be confused about when the rate limit actually resets, especially since GitHub's rate limits reset in UTC.

🌍 Optional enhancement for i18n
 <span style={{ fontSize: 11, color: 'var(--text2)' }}>
   Used: {rateLimit.used} • Reset at{' '}
-  {new Date(rateLimit.reset * 1000).toLocaleTimeString()}
+  {new Date(rateLimit.reset * 1000).toLocaleTimeString(undefined, { 
+    hour: '2-digit', 
+    minute: '2-digit',
+    second: '2-digit',
+    timeZoneName: 'short'
+  })}
 </span>

This adds timezone information (e.g., "4:07:57 PM PST") to help users understand when the reset occurs in their local time.

📝 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
<span style={{ fontSize: 11, color: 'var(--text2)' }}>
Used: {rateLimit.used} Reset at{' '}
{new Date(rateLimit.reset * 1000).toLocaleTimeString()}
</span>
<span style={{ fontSize: 11, color: 'var(--text2)' }}>
Used: {rateLimit.used} Reset at{' '}
{new Date(rateLimit.reset * 1000).toLocaleTimeString(undefined, {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZoneName: 'short'
})}
</span>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/RateLimitBanner.jsx` around lines 36 - 39, The
toLocaleTimeString() method call on the rateLimit.reset date in the
RateLimitBanner component displays only the time without timezone information,
which can confuse users about when the rate limit actually resets. Modify the
toLocaleTimeString() call to include timezone information by passing an options
object as a second parameter that specifies timeZoneName property, so users see
both the local time and their timezone abbreviation (e.g., "4:07:57 PM PST") for
better clarity across different user timezones.

Source: Coding guidelines

Comment on lines +38 to +49
useEffect(() => {
const handler = e => {
setRateLimit(e.detail)
localStorage.setItem('oe_rate_limit', JSON.stringify(e.detail))
}

window.addEventListener('rate-limit-update', handler)

return () => {
window.removeEventListener('rate-limit-update', handler)
}
}, [])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate event payload to prevent malformed rate limit data.

The event handler (lines 39-42) directly uses e.detail without validation. If a malicious script or browser extension dispatches a rate-limit-update event with invalid data, it could corrupt the rate limit state and localStorage.

🔒 Proposed fix with validation
 useEffect(() => {
   const handler = e => {
+    const { limit, remaining, used, reset } = e.detail || {}
+    
+    if (
+      typeof limit !== 'number' || limit < 0 ||
+      typeof remaining !== 'number' || remaining < 0 ||
+      typeof used !== 'number' || used < 0 ||
+      typeof reset !== 'number' || reset <= 0
+    ) {
+      console.warn('Invalid rate-limit-update event payload:', e.detail)
+      return
+    }
+    
     setRateLimit(e.detail)
     localStorage.setItem('oe_rate_limit', JSON.stringify(e.detail))
   }
📝 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
useEffect(() => {
const handler = e => {
setRateLimit(e.detail)
localStorage.setItem('oe_rate_limit', JSON.stringify(e.detail))
}
window.addEventListener('rate-limit-update', handler)
return () => {
window.removeEventListener('rate-limit-update', handler)
}
}, [])
useEffect(() => {
const handler = e => {
const { limit, remaining, used, reset } = e.detail || {}
if (
typeof limit !== 'number' || limit < 0 ||
typeof remaining !== 'number' || remaining < 0 ||
typeof used !== 'number' || used < 0 ||
typeof reset !== 'number' || reset <= 0
) {
console.warn('Invalid rate-limit-update event payload:', e.detail)
return
}
setRateLimit(e.detail)
localStorage.setItem('oe_rate_limit', JSON.stringify(e.detail))
}
window.addEventListener('rate-limit-update', handler)
return () => {
window.removeEventListener('rate-limit-update', handler)
}
}, [])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/context/AppContext.jsx` around lines 38 - 49, The handler function in the
useEffect for the 'rate-limit-update' event listener directly uses e.detail
without validation before calling setRateLimit and storing it in localStorage
with the key 'oe_rate_limit'. Add validation logic to verify that e.detail is a
valid object with the expected structure and properties (such as checking that
it is an object type and has required fields) before proceeding with
setRateLimit and localStorage.setItem. Only allow the state and localStorage
updates to occur if the validation passes.

Comment thread src/services/github.js
Comment on lines +65 to +74
window.dispatchEvent(
new CustomEvent('rate-limit-update', {
detail: {
limit: Number(res.headers.get('x-ratelimit-limit')),
remaining: Number(res.headers.get('x-ratelimit-remaining')),
used: Number(res.headers.get('x-ratelimit-used')),
reset: Number(res.headers.get('x-ratelimit-reset'))
}
})
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add defensive null checks for rate limit headers.

The code converts header values using Number() without checking if the headers exist. If GitHub doesn't send rate limit headers (edge case), Number(null) returns 0, which would incorrectly display "0/0" remaining.

🛡️ Proposed fix with validation
-  window.dispatchEvent(
-    new CustomEvent('rate-limit-update', {
-      detail: {
-        limit: Number(res.headers.get('x-ratelimit-limit')),
-        remaining: Number(res.headers.get('x-ratelimit-remaining')),
-        used: Number(res.headers.get('x-ratelimit-used')),
-        reset: Number(res.headers.get('x-ratelimit-reset'))
-      }
-    })
-  )
+  const limit = res.headers.get('x-ratelimit-limit')
+  const remaining = res.headers.get('x-ratelimit-remaining')
+  const used = res.headers.get('x-ratelimit-used')
+  const reset = res.headers.get('x-ratelimit-reset')
+  
+  if (limit && remaining && used && reset) {
+    window.dispatchEvent(
+      new CustomEvent('rate-limit-update', {
+        detail: {
+          limit: Number(limit),
+          remaining: Number(remaining),
+          used: Number(used),
+          reset: Number(reset)
+        }
+      })
+    )
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/github.js` around lines 65 - 74, Add null checks before
converting rate limit header values to numbers in the custom event dispatch. For
each header retrieval (x-ratelimit-limit, x-ratelimit-remaining,
x-ratelimit-used, x-ratelimit-reset), verify that the header exists before
passing it to Number(). Either check if the header value is null before
conversion or provide sensible default values when headers are missing. This
prevents Number(null) from returning 0 and ensures rate limit information
displays correctly even when headers are absent.

@github-actions github-actions Bot added size/M 51-200 lines changed and removed size/M 51-200 lines changed labels Jun 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

external-contributor External contributor frontend Frontend changes javascript JavaScript/TypeScript changes no-issue-linked PR has no linked issue size/M 51-200 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant