-
Notifications
You must be signed in to change notification settings - Fork 0
Claude/lint fixes UI enhancements 01 awv je hq cutl dm4 m cfh6h th #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Claude/lint fixes UI enhancements 01 awv je hq cutl dm4 m cfh6h th #24
Conversation
- Remove ThemeToggle from PageHeader (moved to sidebar) - Add collapsible sidebar with localStorage persistence * Sidebar can be collapsed to show only icons * State persists across sessions * Smooth transitions for width changes - Add ThemeToggle to sidebar footer - Create global AppHeader component with: * Breadcrumb navigation showing current path * Global search bar with Cmd+K shortcut display * Fixed positioning at top of authenticated pages - Display issue/PR numbers in #ID format in tables * Added to Quick Wins table * Added to Action Required table * Numbers extracted from GitHub URLs
- Create DetailPanel component for displaying issue details * Slide-over panel from right side * Shows issue description, comments, labels, and metadata * Smooth transitions and responsive design * No external markdown dependencies - Create UserHoverCard component with profile info * Shows user avatar, bio, company, location * Displays repos, followers, and following counts * Integration with GitHub API to fetch user profiles - Implement Open Source Score algorithm * Formula: (Commits × 2) + (PRs × 5) + Starred Repos * Calculates contributions from last 3 months * New getUserContributions method in github-api-client * Fetches user events and starred repositories - Add score visualization to UserHoverCard * Gradient progress bar showing score * Displays score with proper formatting * Tooltip explaining the calculation * Beautiful card design with blue/indigo gradient
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. WalkthroughAdds UI features: issue/PR number extraction and inline badges, a sliding Issue DetailPanel, a collapsible sidebar with preferences, breadcrumb AppHeader, a UserHoverCard that fetches contributions, and a new GitHub client method getUserContributions. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI as App (List / Title)
participant Helper as extractIssueNumber
User->>UI: View item list
UI->>Helper: parse item.url
Helper-->>UI: issueNumber or null
alt issueNumber present
UI-->>User: Render inline badge "#<n>" next to title
else
UI-->>User: Render title without badge
end
sequenceDiagram
participant User
participant Hover as UserHoverCard
participant API as GitHub API
User->>Hover: Hover over username
activate Hover
Hover->>API: fetchUserProfile(username)
API-->>Hover: profile
alt showScore == true
Hover->>API: getUserContributions(username)
API-->>Hover: {commits, prs, stars}
Hover->>Hover: calculateOpenSourceScore()
end
Hover-->>User: Render rich profile card
deactivate Hover
sequenceDiagram
participant User
participant Sidebar
participant Store as Preferences Store
participant Layout
User->>Sidebar: Click collapse toggle
Sidebar->>Store: setSidebarCollapsed(!v)
Store-->>Sidebar: new collapsed state
Sidebar-->>Layout: width change / re-render
Layout-->>User: Adjust left margin and content layout
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Areas to focus review on:
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (3)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (8)
src/components/ui/user-hover-card.tsx (2)
38-44: Consider using profile data or removing unused parameter.The
profileparameter is currently unused in the score calculation. If you plan to incorporate profile metrics (repos, followers, etc.) in the future, this is fine. Otherwise, simplify the signature.If not planning to use profile data:
-function calculateOpenSourceScore(profile: UserProfile, contributions?: { commits: number; prs: number; stars: number }): number { +function calculateOpenSourceScore(contributions?: { commits: number; prs: number; stars: number }): number { const commitsScore = (contributions?.commits || 0) * 2; const prsScore = (contributions?.prs || 0) * 5; const starsScore = contributions?.stars || 0; return commitsScore + prsScore + starsScore; }And update the call site on line 62:
- const calculatedScore = calculateOpenSourceScore(userProfile, contributions); + const calculatedScore = calculateOpenSourceScore(contributions);
126-138: Consider validating blog URL format.The current implementation prepends
https://to URLs without the protocol, but doesn't validate if the result is a valid URL. Users might enter invalid formats like email addresses or social media handles.Add basic validation:
{profile.blog && ( <div className="flex items-center gap-2 text-muted-foreground"> <LinkIcon className="w-4 h-4 flex-shrink-0" /> <a - href={profile.blog.startsWith("http") ? profile.blog : `https://${profile.blog}`} + href={(() => { + const blog = profile.blog.trim(); + if (blog.startsWith("http://") || blog.startsWith("https://")) { + return blog; + } + // Basic validation: contains a dot and no spaces + if (blog.includes(".") && !blog.includes(" ")) { + return `https://${blog}`; + } + return "#"; // Invalid URL + })()} target="_blank" rel="noopener noreferrer" className="hover:text-foreground truncate" > {profile.blog} </a> </div> )}src/components/quick-wins/columns.tsx (1)
13-16: Issue number badge implementation is correct; consider sharing the helperThe extraction logic and conditional badge rendering are sound and match GitHub URL patterns. Since an almost identical
extractIssueNumberexists insrc/app/action-required/page.tsx, consider moving this helper to a shared utility (e.g.,utils/github.ts) to avoid duplication and future drift.Also applies to: 32-40
src/app/action-required/page.tsx (1)
56-60: Avoid duplicate helpers and repeated extraction per row
extractIssueNumberis correct but duplicates the helper insrc/components/quick-wins/columns.tsx. Also, within the cell you call it twice for the sameitem.url.You can both DRY and simplify by computing once:
- {extractIssueNumber(item.url) && ( - <span className="text-sm text-gray-500 dark:text-gray-400 font-mono flex-shrink-0"> - #{extractIssueNumber(item.url)} - </span> - )} + {(() => { + const issueNumber = extractIssueNumber(item.url); + return issueNumber ? ( + <span className="text-sm text-gray-500 dark:text-gray-400 font-mono flex-shrink-0"> + #{issueNumber} + </span> + ) : null; + })()}Longer term, consider a shared utility for the extraction logic used here and in
quick-wins/columns.tsx.Also applies to: 310-314
src/components/common/DetailPanel.tsx (1)
61-72: Tighten panel animation effect and timeout handlingThe
useEffectcurrently schedules asetTimeoutwhen opening but never clears it, and on close the component unmounts immediately, so there’s no slide-out animation and a very fast open/close could trigger a state update after unmount.Consider:
- Storing the timeout ID and clearing it in the effect cleanup.
- (Optional) If you want a real exit animation, keep the component mounted while
isVisibletransitions tofalseand only unmount after the transition duration.For example:
- useEffect(() => { - if (isOpen) { - setTimeout(() => setIsVisible(true), 10); - } else { - setIsVisible(false); - } - }, [isOpen]); + useEffect(() => { + let timer: ReturnType<typeof setTimeout> | null = null; + + if (isOpen) { + timer = setTimeout(() => setIsVisible(true), 10); + } else { + setIsVisible(false); + } + + return () => { + if (timer) clearTimeout(timer); + }; + }, [isOpen]);You can then adjust the unmount condition if you decide to support a closing transition.
Also applies to: 82-86
src/components/layout/Layout.tsx (1)
5-7: AppHeader integration and responsive main margin look goodWiring
AppHeaderintoLayoutand tyingmain’slg:ml-16/lg:ml-64tosidebarCollapsedgives a clear, predictable content offset relative to the sidebar.If you later notice unnecessary re-renders when other preferences change, you could narrow the subscription to just
sidebarCollapsed, e.g.usePreferencesStore((s) => s.sidebarCollapsed), but that’s an optimization, not a blocker here.Also applies to: 11-11, 25-30
src/components/layout/AppHeader.tsx (1)
10-35: Breadcrumb + search header is solid; consider a small pathname safety guardThe breadcrumb construction and search button wiring look clean and readable, and
routeLabelsgives nice human-readable titles for the main routes.One small robustness tweak: if
usePathname()ever returnsnull/undefined(depending on Next.js version and environment),pathname.split("/")would throw. You can defensively handle that with:- const pathSegments = pathname + const pathSegments = (pathname ?? "") .split("/") .filter((segment) => segment !== "");Everything else in this header looks good to ship.
Also applies to: 41-60, 62-75
src/components/layout/Sidebar.tsx (1)
253-259: Top-level totals badges don’t reflect loading state (optional UX tweak)
getActionRequiredTotalandgetQuickWinsTotalalways return a number (0 whilehasHydratedis false), so the top-level badges can briefly show0and then jump to the real count, while the leaf badges usegetBadgeContentand show"..."during loading.If you’d like consistent feedback, consider deriving the totals from
getBadgeContentor explicitly checking the loading flags before rendering the totals. For example:- {getActionRequiredTotal} + {loading.assigned || loading.mentions || loading.stale + ? "..." + : getActionRequiredTotal}(Similar pattern for Quick Wins.)
This is purely a polish suggestion; the current logic is functionally correct.
Also applies to: 364-369
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
src/app/action-required/page.tsx(2 hunks)src/components/common/DetailPanel.tsx(1 hunks)src/components/layout/AppHeader.tsx(1 hunks)src/components/layout/Layout.tsx(2 hunks)src/components/layout/PageHeader.tsx(0 hunks)src/components/layout/Sidebar.tsx(6 hunks)src/components/quick-wins/columns.tsx(2 hunks)src/components/ui/user-hover-card.tsx(1 hunks)src/lib/api/github-api-client.ts(1 hunks)
💤 Files with no reviewable changes (1)
- src/components/layout/PageHeader.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
src/components/ui/user-hover-card.tsx (2)
src/app/api/auth/[...nextauth]/route.ts (1)
profile(53-67)src/lib/api/github-api-client.ts (1)
githubAPIClient(1373-1373)
src/components/layout/Sidebar.tsx (2)
src/stores/preferences.ts (1)
usePreferencesStore(93-271)src/components/theme/ThemeToggle.tsx (1)
ThemeToggle(14-38)
src/components/layout/Layout.tsx (3)
src/stores/index.ts (2)
useSidebarState(176-179)usePreferencesStore(9-9)src/stores/preferences.ts (1)
usePreferencesStore(93-271)src/components/layout/AppHeader.tsx (1)
AppHeader(19-79)
🔇 Additional comments (5)
src/components/ui/user-hover-card.tsx (1)
163-184: Nice implementation of the Open Source score display!The score card with gradient styling, progress bar animation, and explanatory tooltip provides a polished user experience. The progress bar calculation with
Math.min((score / 1000) * 100, 100)properly caps at 100% and the title attribute aids accessibility.src/components/common/DetailPanel.tsx (1)
10-35: Well-typed, safe detail rendering
IssueDetailandDetailPanelPropsline up with how the fields are used, and the panel guards optional data (issue,labels,assignees,comments) safely. Body and comment text are rendered as plain JSX (escaped), which avoids XSS issues, and the layout looks responsive (w-fullon small screens, fixed width on larger). This is a solid base for the issue detail UI.Also applies to: 51-59, 101-235
src/components/layout/Sidebar.tsx (3)
12-13: Sidebar preferences store and ThemeToggle wiring look correct
usePreferencesStoreusage (sidebarCollapsed,setSidebarCollapsed) matches the store API, and theThemeToggleimport/usage is consistent with its public interface. This should persist collapse state and theme preferences cleanly without extra plumbing here.Also applies to: 32-32, 41-41
179-198: Header behavior in collapsed vs expanded states looks goodThe header cleanly switches between full branding plus close button and the compact “G” logo based on
sidebarCollapsed, with the mobile close control still available vialg:hidden. This is a straightforward and clear implementation.
492-508: Logout button alignment and loading state handling look solidConditionally centering vs left-aligning the logout button based on
sidebarCollapsed, plus swapping icon/text and disabling the button whileisLoggingOutis true, provides a clear and safe UX. The existinghasHydrated && isConnectedguard still prevents flicker.
| <aside | ||
| className={` | ||
| fixed top-0 left-0 w-64 bg-sidebar border-r border-sidebar-border z-50 transform transition-transform duration-300 ease-in-out | ||
| fixed top-0 left-0 bg-sidebar border-r border-sidebar-border z-50 transform transition-all duration-300 ease-in-out | ||
| ${sidebarCollapsed ? "w-16" : "w-64"} | ||
| ${isOpen ? "translate-x-0" : "-translate-x-full"} | ||
| lg:translate-x-0 | ||
| flex flex-col | ||
| flex flex-col | ||
| h-screen | ||
| `} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Collapsed width persists on mobile with no way to expand the sidebar
Because the width class is unconditional (w-16 vs w-64) and the collapse toggle button is hidden lg:flex, a user who collapses the sidebar on desktop will get a very narrow, icon-only sidebar on mobile with no visible control to expand it again. That makes the mobile navigation hard to use.
Consider scoping the narrow width to desktop so mobile always uses the full width:
- ${sidebarCollapsed ? "w-16" : "w-64"}
+ ${sidebarCollapsed ? "lg:w-16 w-64" : "w-64"}This keeps the sidebar full-width on small screens while still allowing a narrow collapsed variant on lg+. You could also optionally make the collapse button visible on mobile if you want collapse to be controllable there as well.
Also applies to: 467-483
| <Link | ||
| href="/dashboard" | ||
| className={`flex items-center gap-3 px-3 py-2 rounded-lg transition-colors | ||
| className={`flex items-center ${sidebarCollapsed ? "justify-center" : "gap-3"} px-3 py-2 rounded-lg transition-colors | ||
| ${ | ||
| pathname === "/dashboard" | ||
| ? "bg-sidebar-accent text-sidebar-accent-foreground" | ||
| : "hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground" | ||
| }`} | ||
| title={sidebarCollapsed ? "Dashboard" : ""} | ||
| > | ||
| <Home className="w-5 h-5" /> | ||
| <span>Dashboard</span> | ||
| {!sidebarCollapsed && <span>Dashboard</span>} | ||
| </Link> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Collapsed sidebar items rely on icons + title, which is weak for accessibility
In the collapsed state, several interactive elements (Dashboard, Action Required, Quick Wins, Settings, Favorites, collapse toggle, Logout) become icon-only and rely solely on the title attribute for labeling. Many screen readers don’t reliably expose title, and the icons themselves are typically aria-hidden, so these controls may be hard or impossible to identify for assistive tech users.
Recommend adding explicit accessible labels (and/or sr-only text), for example:
- <Link
- href="/dashboard"
- className={`flex items-center ${sidebarCollapsed ? "justify-center" : "gap-3"} px-3 py-2 rounded-lg transition-colors
- ${pathname === "/dashboard"
- ? "bg-sidebar-accent text-sidebar-accent-foreground"
- : "hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground"
- }`}
- title={sidebarCollapsed ? "Dashboard" : ""}
- >
- <Home className="w-5 h-5" />
- {!sidebarCollapsed && <span>Dashboard</span>}
- </Link>
+ <Link
+ href="/dashboard"
+ className={`flex items-center ${sidebarCollapsed ? "justify-center" : "gap-3"} px-3 py-2 rounded-lg transition-colors
+ ${pathname === "/dashboard"
+ ? "bg-sidebar-accent text-sidebar-accent-foreground"
+ : "hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground"
+ }`}
+ aria-label="Dashboard"
+ >
+ <Home className="w-5 h-5" aria-hidden="true" />
+ {!sidebarCollapsed && <span>Dashboard</span>}
+ </Link>You can apply the same pattern (using aria-label and aria-hidden on icons, plus sr-only labels if preferred) to the other icon-only links/buttons in the collapsed footer and nav.
Also applies to: 220-233, 333-346, 424-450, 467-483, 492-500
🤖 Prompt for AI Agents
In src/components/layout/Sidebar.tsx around lines 205 to 217 (and also apply the
same fix to ranges 220-233, 333-346, 424-450, 467-483, 492-500), the collapsed
sidebar items are icon-only and rely on title attributes which are not reliable
for screen readers; update each icon-only Link/Button to include an explicit
accessible label (for example add an aria-label prop with the visible name),
keep the icon as aria-hidden="true", and optionally include a visually hidden
text element (sr-only) inside the control for screen readers when you prefer not
to show text visually; ensure active/hover classes remain unchanged and that
aria-labels match the visible labels for proper accessibility.
| try { | ||
| const userProfile = await githubAPIClient.getUserProfile(username); | ||
| setProfile(userProfile); | ||
|
|
||
| if (showScore) { | ||
| const contributions = await githubAPIClient.getUserContributions(username); | ||
| const calculatedScore = calculateOpenSourceScore(userProfile, contributions); | ||
| setScore(calculatedScore); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type safety issue: potential null reference and type mismatch.
Two issues here:
-
Type mismatch:
getUserProfilereturnsGitHubUserDetailed | null, but the component's state expectsUserProfile. These interfaces may not be structurally compatible. -
Null reference: If
getUserProfilereturnsnull, line 62 will passnulltocalculateOpenSourceScore(userProfile, contributions), which expects aUserProfileobject.
Apply this diff to fix both issues:
setLoading(true);
try {
const userProfile = await githubAPIClient.getUserProfile(username);
- setProfile(userProfile);
+ if (!userProfile) {
+ setProfile(null);
+ return;
+ }
+ setProfile(userProfile as UserProfile);
if (showScore) {
const contributions = await githubAPIClient.getUserContributions(username);
const calculatedScore = calculateOpenSourceScore(userProfile as UserProfile, contributions);
setScore(calculatedScore);
}
} catch (error) {Alternatively, align the component's interface with the API's return type:
-import { githubAPIClient } from "@/lib/api/github-api-client";
+import { githubAPIClient } from "@/lib/api/github-api-client";
+import type { GitHubUserDetailed } from "@/types/github";
-export interface UserProfile {
- login: string;
- name: string | null;
- avatar_url: string;
- // ... rest of fields
-}
+export type UserProfile = GitHubUserDetailed;
interface UserHoverCardProps {
username: string;
children: React.ReactNode;
showScore?: boolean;
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/components/ui/user-hover-card.tsx around lines 56 to 64, the code assumes
githubAPIClient.getUserProfile returns a non-null UserProfile but the API
returns GitHubUserDetailed | null, which can cause a type mismatch and null
reference when calling calculateOpenSourceScore; fix it by 1) updating the
component state type to accept GitHubUserDetailed | null (or map
GitHubUserDetailed to the existing UserProfile shape) so types align, 2) only
call setProfile and compute score when the returned userProfile is non-null, and
3) guard the calculateOpenSourceScore call (and any access to profile fields)
behind that null check so you never pass null into a function expecting
UserProfile.
| if (event.type === "PushEvent") { | ||
| commits += 1; | ||
| } else if (event.type === "PullRequestEvent") { | ||
| prs += 1; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Misleading commit count - counts events, not actual commits.
A PushEvent can contain multiple commits, but this code counts each event as a single commit. This significantly undercounts actual contribution activity and makes the returned commits value semantically incorrect.
Consider one of these approaches:
Option 1: Access the payload to count actual commits:
if (Array.isArray(events)) {
for (const event of events) {
const eventDate = new Date(event.created_at);
if (eventDate < threeMonthsAgo) continue;
if (event.type === "PushEvent") {
- commits += 1;
+ commits += (event.payload?.commits?.length || 1);
} else if (event.type === "PullRequestEvent") {
prs += 1;
}
}
}Update the type on line 861:
- const events = await this.fetchWithCache<Array<{ type: string; created_at: string }>>(
+ const events = await this.fetchWithCache<Array<{ type: string; created_at: string; payload?: { commits?: unknown[] } }>>(Option 2: Rename to clarify it counts events, not commits:
- async getUserContributions(username: string): Promise<{ commits: number; prs: number; stars: number }> {
+ async getUserContributions(username: string): Promise<{ pushEvents: number; prEvents: number; stars: number }> {Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/lib/api/github-api-client.ts around lines 874 to 878, the code increments
commits by one per PushEvent which is misleading because a PushEvent can contain
multiple commits; either (A) change logic to sum the actual commits from the
event payload (e.g., use event.payload.commits.length or event.payload?.size
depending on typed payload) and update the event type definition at line 861 so
payload.commits is accessible and typed safely, or (B) if you don’t want to
inspect payloads, rename the variable from commits to pushEventCount (and adjust
any callers/return values and docs) so it clearly reflects that you’re counting
PushEvents not commits. Ensure null/undefined payloads are guarded and
tests/consumers are updated accordingly.
| const starredEndpoint = `/users/${username}/starred?per_page=1`; | ||
| const response = await fetch(`https://api.github.com${starredEndpoint}`, { | ||
| headers: this.getHeaders(), | ||
| }); | ||
| const linkHeader = response.headers.get("link"); | ||
| let stars = 0; | ||
| if (linkHeader) { | ||
| const match = linkHeader.match(/page=(\d+)>; rel="last"/); | ||
| if (match) { | ||
| stars = parseInt(match[1], 10); | ||
| } | ||
| } else { | ||
| const starred = await response.json(); | ||
| stars = Array.isArray(starred) ? starred.length : 0; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Use fetchWithCache for consistency and efficiency.
This direct fetch call bypasses the caching mechanism used throughout the rest of the class, leading to unnecessary API calls and inconsistent behavior.
Since you only need the Link header for pagination count, consider using a HEAD request with caching:
- const starredEndpoint = `/users/${username}/starred?per_page=1`;
- const response = await fetch(`${this.baseUrl}${starredEndpoint}`, { headers });
- const linkHeader = response.headers.get("link");
- let stars = 0;
- if (linkHeader) {
- const match = linkHeader.match(/page=(\d+)>; rel="last"/);
- if (match) {
- stars = parseInt(match[1], 10);
- }
- } else {
- const starred = await response.json();
- stars = Array.isArray(starred) ? starred.length : 0;
- }
+ // Try to get starred count from user profile first (cheaper)
+ const profileEndpoint = `/users/${username}`;
+ const profileData = await this.fetchWithCache<{ public_repos: number; followers: number }>(
+ profileEndpoint,
+ true
+ );
+
+ // Note: GitHub API doesn't expose starred count in profile
+ // So we still need to fetch starred repos page for accurate count
+ const starredEndpoint = `/users/${username}/starred?per_page=100`;
+ const starred = await this.fetchWithCache<unknown[]>(starredEndpoint, true);
+ let stars = Array.isArray(starred) ? starred.length : 0;Note: The GitHub API doesn't include a public_stars field in the user profile, so you'll need to either fetch a page of starred repos (and accept approximate counts for users with 100+ stars) or make a HEAD request separately (which won't benefit from JSON caching).
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/lib/api/github-api-client.ts around lines 882 to 896, the code calls
fetch directly to get a user's starred endpoint which bypasses the class's
caching and causes extra API calls; replace the direct fetch with the existing
fetchWithCache wrapper (or a cached HEAD request) so the response is cached and
consistent: use fetchWithCache to request the
/users/:username/starred?per_page=1 endpoint (or perform a cached HEAD to read
the Link header if HEAD is acceptable), extract the Link header from the cached
response to determine pagination/total pages, and only fall back to parsing
response.json() when no Link header is present; ensure you pass the same headers
(this.getHeaders()) into fetchWithCache and preserve existing error handling.
Critical: Undefined method call will cause runtime error.
Line 884 calls this.getHeaders(), but this method does not exist in the GitHubAPIClient class.
Apply this diff to fix the issue and align with the existing patterns in the class:
- const starredEndpoint = `/users/${username}/starred?per_page=1`;
- const response = await fetch(`https://api.github.com${starredEndpoint}`, {
- headers: this.getHeaders(),
- });
+ const starredEndpoint = `/users/${username}/starred?per_page=1`;
+ const headers: HeadersInit = {
+ Accept: "application/vnd.github.v3+json",
+ "User-Agent": "GitHubMon/1.0",
+ };
+ if (this.githubToken) {
+ headers["Authorization"] = `Bearer ${this.githubToken}`;
+ }
+ const response = await fetch(`${this.baseUrl}${starredEndpoint}`, { headers });📝 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.
| const starredEndpoint = `/users/${username}/starred?per_page=1`; | |
| const response = await fetch(`https://api.github.com${starredEndpoint}`, { | |
| headers: this.getHeaders(), | |
| }); | |
| const linkHeader = response.headers.get("link"); | |
| let stars = 0; | |
| if (linkHeader) { | |
| const match = linkHeader.match(/page=(\d+)>; rel="last"/); | |
| if (match) { | |
| stars = parseInt(match[1], 10); | |
| } | |
| } else { | |
| const starred = await response.json(); | |
| stars = Array.isArray(starred) ? starred.length : 0; | |
| } | |
| const starredEndpoint = `/users/${username}/starred?per_page=1`; | |
| const headers: HeadersInit = { | |
| Accept: "application/vnd.github.v3+json", | |
| "User-Agent": "GitHubMon/1.0", | |
| }; | |
| if (this.githubToken) { | |
| headers["Authorization"] = `Bearer ${this.githubToken}`; | |
| } | |
| const response = await fetch(`${this.baseUrl}${starredEndpoint}`, { headers }); | |
| const linkHeader = response.headers.get("link"); | |
| let stars = 0; | |
| if (linkHeader) { | |
| const match = linkHeader.match(/page=(\d+)>; rel="last"/); | |
| if (match) { | |
| stars = parseInt(match[1], 10); | |
| } | |
| } else { | |
| const starred = await response.json(); | |
| stars = Array.isArray(starred) ? starred.length : 0; | |
| } |
🤖 Prompt for AI Agents
In src/lib/api/github-api-client.ts around lines 882 to 896, the code calls a
non-existent this.getHeaders() method; replace that call with the class's
existing header helper (use this.getRequestHeaders()) and ensure the call passes
the same context (no new params) so the fetch uses the correct auth/accept
headers; if getRequestHeaders does not exist, add a simple private
getRequestHeaders() that returns the same header object pattern used elsewhere
in the class (Authorization/Accept/User-Agent) and use it here.
…enhancements-01AWVJeHqCUTLDm4MCfh6hTH
Summary by CodeRabbit
New Features
UI/UX Improvements