Skip to content

Add PR review tracking via GitHub Search API#28

Open
kertal wants to merge 7 commits into
mainfrom
claude/improve-pr-review-data-3Xz99
Open

Add PR review tracking via GitHub Search API#28
kertal wants to merge 7 commits into
mainfrom
claude/improve-pr-review-data-3Xz99

Conversation

@kertal

@kertal kertal commented Feb 19, 2026

Copy link
Copy Markdown
Owner

Summary

This PR adds comprehensive PR review tracking by leveraging the GitHub Search API's reviewed-by: query. This provides more accurate and complete review data compared to the Events API, which is limited to ~300 events and 30 days of history.

Key Changes

  • New review data pipeline: Added indexedDBReviewItems storage and processing throughout the data flow (App.tsx, useGitHubDataFetching.ts, useGitHubDataProcessing.ts)

  • Search API integration: Implemented reviewed-by: query in useGitHubDataFetching.ts to fetch PRs reviewed by each user within the specified date range, with pagination support and error handling

  • Summary view enhancement: Extended groupSummaryData() in summaryGrouping.ts to include reviewed PRs via new addReviewedPRsFromSearchItems() function, which deduplicates against existing PR data

  • Data processing: Added review items processing in useGitHubDataProcessing.ts to categorize and filter review data alongside other GitHub items

  • Test updates: Updated useGitHubDataFetching.test.ts to include mock functions for review item storage and clearing

Implementation Details

  • Review data is fetched using the same pagination pattern as search items (per_page=30, max 1000 items per user)
  • Includes rate limiting delays between paginated requests
  • Gracefully handles API failures without interrupting the overall data fetch
  • Review items are deduplicated by base PR URL to avoid duplicates in the summary view
  • Review data is stored separately in IndexedDB under the key 'github-review-items-indexeddb'

https://claude.ai/code/session_01C3KBpRv5F5LneNE8jKRDYN

Use the Search API `reviewed-by:{username} is:pr` query to fetch PRs
reviewed by each user, providing more accurate and comprehensive review
data than the Events API which is limited to ~300 events and 30 days.

Review items are fetched, stored in a separate IndexedDB key, and merged
into the Summary view's "PRs - reviewed" group with deduplication against
existing Events API review data.

https://claude.ai/code/session_01C3KBpRv5F5LneNE8jKRDYN

Copilot AI left a comment

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.

Pull request overview

Adds a new “review tracking” pipeline that uses the GitHub Search API reviewed-by: qualifier so the Summary view can include PRs reviewed by each user beyond the Events API history limits.

Changes:

  • Introduces IndexedDB-backed storage and processing for “review items” alongside existing events/search items.
  • Integrates reviewed-by: Search API fetching with pagination and persistence.
  • Extends Summary grouping to incorporate reviewed PRs from Search results.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/App.tsx Wires a new IndexedDB store for review items and passes review data into Summary view.
src/hooks/useGitHubDataFetching.ts Adds reviewed-by: Search API fetching, accumulation, sorting, and persistence of reviewed PR items.
src/hooks/useGitHubDataProcessing.ts Adds processing for review items and exposes them to the app for summary usage.
src/views/Summary.tsx Accepts review items, filters them with the header search, and passes them to summary grouping.
src/utils/summaryGrouping.ts Adds reviewed-PR aggregation from Search items and extends groupSummaryData() signature.
src/hooks/useGitHubDataFetching.test.ts Updates hook test setup to include new review-item store/clear mocks.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +217 to +237
/**
* Adds reviewed PRs from the Search API `reviewed-by:` query results.
* These are more accurate and comprehensive than the Events API review data,
* which is limited to ~300 events and 30 days.
*/
export const addReviewedPRsFromSearchItems = (
groups: Record<SummaryGroupName, GitHubItem[]>,
reviewItems: GitHubItem[],
): void => {
const existingReviewUrls = new Set(
groups[SUMMARY_GROUP_NAMES.PRS_REVIEWED].map(item => getBasePRUrl(item.html_url))
);

reviewItems.forEach(reviewItem => {
const baseUrl = getBasePRUrl(reviewItem.html_url);
if (!existingReviewUrls.has(baseUrl)) {
existingReviewUrls.add(baseUrl);
groups[SUMMARY_GROUP_NAMES.PRS_REVIEWED].push(reviewItem);
}
});
};

Copilot AI Feb 19, 2026

Copy link

Choose a reason for hiding this comment

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

addReviewedPRsFromSearchItems introduces new reviewed-PR aggregation/deduplication behavior, but there are existing unit tests for summaryGrouping and none appear to cover this new function. Add tests to verify (1) reviewed PRs are added to PRS_REVIEWED, (2) deduplication works as intended (including fragments via getBasePRUrl), and (3) multiple reviewers on the same PR are handled correctly.

Copilot uses AI. Check for mistakes.
Comment thread src/App.tsx Outdated
items={results}
rawEvents={indexedDBEvents}
indexedDBSearchItems={indexedDBSearchItems as unknown as GitHubItem[]}
indexedDBReviewItems={reviewItems}

Copilot AI Feb 19, 2026

Copy link

Choose a reason for hiding this comment

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

SummaryView’s prop is named indexedDBReviewItems, but App is passing reviewItems (already processed/categorized output from useGitHubDataProcessing), unlike indexedDBSearchItems which is passed as raw IndexedDB data. Consider renaming this prop/value (e.g., reviewedPRItems / processedReviewItems) or passing the raw IndexedDB array consistently to avoid confusion about what stage of the pipeline this data is in.

Suggested change
indexedDBReviewItems={reviewItems}
indexedDBReviewItems={indexedDBReviewItems}

Copilot uses AI. Check for mistakes.
Comment on lines +281 to +286
const reviewItemsWithOriginal = reviewData.items.map((item: Record<string, unknown>) => ({
...item,
assignee: item.assignee || null,
assignees: item.assignees || [],
original: item,
}));

Copilot AI Feb 19, 2026

Copy link

Choose a reason for hiding this comment

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

The reviewed-by: Search API results are PR objects where item.user is the PR author and title is the PR title. Pushing these directly into the “PRs - reviewed” bucket will attribute the review to the wrong user and won’t match existing review handling that relies on title starting with "Review on:" (and/or originalEventType). Consider transforming each reviewed PR into a review-style GitHubItem (set title to "Review on: …", set user to the reviewer singleUsername, and preserve the original PR fields separately) so the summary displays and deduping behave consistently.

Suggested change
const reviewItemsWithOriginal = reviewData.items.map((item: Record<string, unknown>) => ({
...item,
assignee: item.assignee || null,
assignees: item.assignees || [],
original: item,
}));
const reviewItemsWithOriginal = reviewData.items.map((item: any) => {
const original = {
...item,
assignee: item.assignee || null,
assignees: item.assignees || [],
};
const originalTitle = typeof item.title === 'string' ? item.title : '';
return {
...original,
// Represent this search result as a review-style item
title: `Review on: ${originalTitle}`,
// Attribute the review to the reviewer, not the PR author
user: singleUsername,
// Preserve the original PR data separately
original,
} as GitHubItem;
});

Copilot uses AI. Check for mistakes.
Comment thread src/utils/summaryGrouping.ts Outdated
Comment on lines +226 to +233
const existingReviewUrls = new Set(
groups[SUMMARY_GROUP_NAMES.PRS_REVIEWED].map(item => getBasePRUrl(item.html_url))
);

reviewItems.forEach(reviewItem => {
const baseUrl = getBasePRUrl(reviewItem.html_url);
if (!existingReviewUrls.has(baseUrl)) {
existingReviewUrls.add(baseUrl);

Copilot AI Feb 19, 2026

Copy link

Choose a reason for hiding this comment

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

addReviewedPRsFromSearchItems dedupes reviewed PRs only by base PR URL. This will collapse reviews from different reviewers (e.g., when multiple usernames are queried) into a single entry and can also conflict with the existing per-reviewer dedupe logic used for Events-based review items. Deduplicate using a reviewer+PR key (consistent with categorizeItem’s ${user}:${basePRUrl} approach) so multiple reviewers on the same PR are preserved.

Suggested change
const existingReviewUrls = new Set(
groups[SUMMARY_GROUP_NAMES.PRS_REVIEWED].map(item => getBasePRUrl(item.html_url))
);
reviewItems.forEach(reviewItem => {
const baseUrl = getBasePRUrl(reviewItem.html_url);
if (!existingReviewUrls.has(baseUrl)) {
existingReviewUrls.add(baseUrl);
// Deduplicate by reviewer+PR, consistent with categorizeItem's `${user}:${basePRUrl}` key
const existingReviewKeys = new Set(
groups[SUMMARY_GROUP_NAMES.PRS_REVIEWED].map(item => {
const baseUrl = getBasePRUrl(item.html_url);
// Derive reviewer identifier; fall back defensively if shape varies
const anyItem = item as any;
const reviewer =
(anyItem.user && anyItem.user.login) ||
(anyItem.actor && anyItem.actor.login) ||
'';
return `${reviewer}:${baseUrl}`;
})
);
reviewItems.forEach(reviewItem => {
const baseUrl = getBasePRUrl(reviewItem.html_url);
const anyReviewItem = reviewItem as any;
const reviewer =
(anyReviewItem.user && anyReviewItem.user.login) ||
(anyReviewItem.actor && anyReviewItem.actor.login) ||
'';
const key = `${reviewer}:${baseUrl}`;
if (!existingReviewKeys.has(key)) {
existingReviewKeys.add(key);

Copilot uses AI. Check for mistakes.
Comment thread src/hooks/useGitHubDataFetching.ts Outdated
Comment on lines +352 to +360
if (sortedReviewItems.length > 0) {
await storeReviewItems('github-review-items-indexeddb', sortedReviewItems as unknown as GitHubEvent[], {
lastFetch: Date.now(),
usernames: usernames,
apiMode: 'search',
startDate,
endDate,
});
}

Copilot AI Feb 19, 2026

Copy link

Choose a reason for hiding this comment

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

Review items are only persisted when sortedReviewItems.length > 0. If a user has no reviewed PRs in the range (or the review search call fails), this leaves any previously stored review items in IndexedDB and the summary can show stale “PRs - reviewed” data. Store an empty array (with updated metadata) or explicitly clear the review-items key on each fetch so storage reflects the latest run.

Suggested change
if (sortedReviewItems.length > 0) {
await storeReviewItems('github-review-items-indexeddb', sortedReviewItems as unknown as GitHubEvent[], {
lastFetch: Date.now(),
usernames: usernames,
apiMode: 'search',
startDate,
endDate,
});
}
await storeReviewItems('github-review-items-indexeddb', sortedReviewItems as unknown as GitHubEvent[], {
lastFetch: Date.now(),
usernames: usernames,
apiMode: 'search',
startDate,
endDate,
});

Copilot uses AI. Check for mistakes.
Comment thread src/hooks/useGitHubDataProcessing.ts Outdated
Comment on lines +9 to +10
indexedDBSearchItems: GitHubEvent[];
indexedDBReviewItems: GitHubEvent[];

Copilot AI Feb 19, 2026

Copy link

Choose a reason for hiding this comment

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

indexedDBReviewItems (and indexedDBSearchItems) are typed as GitHubEvent[], but they actually hold Search API issue/PR objects (GitHubItem). This forces unsafe as unknown as GitHubItem[] casts and makes it easy to accidentally treat search results like events. Consider making useIndexedDBStorage generic (or creating a separate storage hook/type) so these props can be GitHubItem[] end-to-end and the casts can be removed.

Suggested change
indexedDBSearchItems: GitHubEvent[];
indexedDBReviewItems: GitHubEvent[];
indexedDBSearchItems: GitHubItem[];
indexedDBReviewItems: GitHubItem[];

Copilot uses AI. Check for mistakes.
Comment on lines 18 to 37
@@ -24,11 +26,14 @@ describe('useGitHubDataFetching', () => {
endDate: '2024-01-31',
indexedDBEvents: [],
indexedDBSearchItems: [],
indexedDBReviewItems: [],
onError: mockOnError,
storeEvents: mockStoreEvents,
clearEvents: mockClearEvents,
storeSearchItems: mockStoreSearchItems,
clearSearchItems: mockClearSearchItems,
storeReviewItems: mockStoreReviewItems,
clearReviewItems: mockClearReviewItems,
};

Copilot AI Feb 19, 2026

Copy link

Choose a reason for hiding this comment

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

The new reviewed-PR fetch path isn’t covered by tests: useGitHubDataFetching.test.ts only adds mocks but doesn’t assert the additional Search API call, pagination behavior, or that storeReviewItems is invoked (including the non-OK response path). Add unit tests that mock fetch responses for the reviewed-by query and verify the hook stores the expected review items/metadata and handles failures without aborting the whole fetch.

Copilot uses AI. Check for mistakes.
Add reviewedBy field to GitHubItem and populate it from both data sources:
- Search API (reviewed-by query): reviewer is the searched username, user is PR author
- Events API (PullRequestReviewEvent): reviewer is the event actor, PR author from payload

ItemRow now shows an AvatarStack with reviewer first, PR author second when
the two users differ.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Feb 19, 2026

Copy link
Copy Markdown

Deploying git-vegas with  Cloudflare Pages  Cloudflare Pages

Latest commit: d0ab04d
Status: ✅  Deploy successful!
Preview URL: https://4487bdfa.git-vegas.pages.dev
Branch Preview URL: https://claude-improve-pr-review-dat.git-vegas.pages.dev

View logs

Valentin and others added 2 commits February 19, 2026 12:13
Critical fixes:
- groupItemsByUrl now detects Search API review items via reviewedBy
  field, not just title prefix, preventing multi-reviewer collapse
- categorizeRawSearchItems uses reviewer+url composite key for review
  items, preserving multiple reviewers on the same PR
- addReviewedPRsFromSearchItems deduplicates by reviewer+url key,
  consistent with categorizeItem's approach

Other fixes:
- Rename confusing prop indexedDBReviewItems → reviewedPRs in Summary
- Always store review items (even empty) to prevent stale data
- Remove dead code after throw in fetchAllEvents
- Fix double space in search query string

Tests:
- Add 8 tests for addReviewedPRsFromSearchItems covering add, dedup,
  multi-reviewer, fragment handling, and groupSummaryData integration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Events API review data (PullRequestReviewEvent) is limited to ~300 events
and 30 days, making it redundant now that we have the Search API
reviewed-by query. Skip "Review on:" items in categorizeItem instead of
merging both sources, and remove the now-unused addedReviewPRs parameter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@kertal

kertal commented Feb 20, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Feb 20, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Feb 20, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

IndexedDB-backed review-item storage and plumbing added: fetching collects reviewed PRs and persists them; processing consumes indexed review items and exposes reviewItems; Summary view now accepts filtered reviewedPRs for grouping; reviewer metadata and reviewer+URL deduplication introduced.

Changes

Cohort / File(s) Summary
App + IndexedDB storage
src/App.tsx, src/hooks/useIndexedDBStorage.ts
Add generic useIndexedDBStorage<T> usage for review items; expose indexedDBReviewItems, storeReviewItems, clearReviewItems at app level.
Data fetching
src/hooks/useGitHubDataFetching.ts, src/hooks/useGitHubDataFetching.test.ts
Extend fetching to accept indexedDBReviewItems, storeReviewItems, clearReviewItems; query for reviewed PRs, accumulate/enrich/sort review items, clear/store review data, and emit progress. Tests updated with review-item mocks.
Data processing
src/hooks/useGitHubDataProcessing.ts
Add indexedDBReviewItems input and compute/return reviewItems (via categorizeRawSearchItems); change search item types to GitHubItem[].
Types
src/types.ts
Add optional reviewedBy?: { login; avatar_url; html_url } and reviewed_at?: string to GitHubItem; add onClearReviewItems?: () => void to SettingsDialogProps.
Raw transforms & dedupe
src/utils/rawDataUtils.ts, src/utils/reviewDates.ts
Transform review events to set reviewedBy and favor PR author as user when distinct; dedupe review items by reviewer+URL; new enrichReviewItemsWithDates fetches review submission dates via GraphQL and attaches reviewed_at.
Summary grouping
src/utils/summaryGrouping.ts, src/utils/__tests__/summaryGrouping.test.ts
Remove inline review-dedupe from categorizeItem; add addReviewedPRsFromSearchItems to populate PRs-REVIEWED with reviewer+URL dedupe; groupSummaryData accepts reviewItems. Tests added/updated.
UI components
src/components/ItemRow.tsx, src/views/Summary.tsx, src/components/SettingsDialog.tsx
ItemRow adds hasDistinctReviewer and prefers reviewer+author avatars; SummaryView gains reviewedPRs prop, filters and groups reviewed PRs; SettingsDialog accepts onClearReviewItems and invokes it on Clear All.

Sequence Diagram

sequenceDiagram
    participant User
    participant App as App.tsx
    participant IDB as IndexedDB\n(useIndexedDBStorage)
    participant Fetch as useGitHubDataFetching
    participant API as GitHub REST\n(GitHub Search API)
    participant GQL as GitHub GraphQL\n(enrichReviewItemsWithDates)
    participant Process as useGitHubDataProcessing
    participant Summary as SummaryView

    User->>App: mount / trigger sync
    App->>IDB: init storage for review items
    IDB-->>App: indexedDBReviewItems, storeReviewItems, clearReviewItems

    App->>Fetch: start fetch (include indexedDBReviewItems & callbacks)
    Fetch->>API: Search API queries for reviewed PRs per user
    API-->>Fetch: search results
    Fetch->>Fetch: accumulate & sort review items
    Fetch->>GQL: enrichReviewItemsWithDates(sortedReviewItems, token)
    GQL-->>Fetch: review items with reviewed_at
    Fetch->>IDB: storeReviewItems(sortedEnrichedReviewItems, metadata)
    IDB-->>Fetch: ack

    App->>Process: process data (include indexedDBReviewItems)
    Process->>Process: categorize review items -> reviewItems
    Process-->>App: reviewItems

    App->>Summary: pass reviewedPRs={reviewItems}
    Summary->>Summary: filter, group (addReviewedPRsFromSearchItems)
    Summary-->>User: render grouped summary including PRS_REVIEWED
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hopped through IndexedDB's green rows,
I matched reviewers to PRs where the wind blows,
I fetched dates by GraphQL, deduped by reviewer+link,
Stored, processed, grouped — all in a blink,
Now Summary dances with reviewed shows!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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
Title check ✅ Passed The title 'Add PR review tracking via GitHub Search API' directly and clearly summarizes the main change: implementing PR review tracking using the GitHub Search API, which is the primary objective of this PR.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing the new review data pipeline, Search API integration, summary view enhancements, data processing updates, and test modifications—all of which are present in the actual changes.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/improve-pr-review-data-3Xz99

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

@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: 1

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

Inline comments:
In `@src/App.tsx`:
- Around line 149-157: Add a new onClearReviewItems callback prop to
SettingsDialogProps and wire it through App.tsx: pass the existing
clearReviewItems function (from the data fetching hook) into the SettingsDialog
alongside onClearEvents and onClearSearchItems, and update the SettingsDialog
implementation to invoke onClearReviewItems when the user triggers "clear
cache." Locate the SettingsDialogProps type and the SettingsDialog component to
add the new prop, update the place in App.tsx where SettingsDialog is rendered
to include onClearReviewItems={clearReviewItems}, and ensure any cache-clear
handler inside SettingsDialog calls props.onClearReviewItems() in the same
sequence as onClearEvents and onClearSearchItems.

Comment thread src/App.tsx
Comment on lines +149 to 157
indexedDBReviewItems,
onError: setError,
storeEvents,
clearEvents,
storeSearchItems,
clearSearchItems,
storeReviewItems,
clearReviewItems,
});

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether review-item caches are cleared in SettingsDialog or elsewhere.
rg -n "clearReviewItems|onClearReviewItems|review-items-indexeddb" src

Repository: kertal/git-vegas

Length of output: 732


🏁 Script executed:

rg -n "SettingsDialog" -g "*.tsx" src

Repository: kertal/git-vegas

Length of output: 550


🏁 Script executed:

rg -n "onClearSearchItems|onClearEvents" -g "*.tsx" src

Repository: kertal/git-vegas

Length of output: 556


🏁 Script executed:

rg -n "clearReviewItems|clearSearchItems" src/hooks/useGitHubDataFetching.ts

Repository: kertal/git-vegas

Length of output: 321


🏁 Script executed:

sed -n '470,480p' src/App.tsx

Repository: kertal/git-vegas

Length of output: 404


🏁 Script executed:

sed -n '19,30p' src/components/SettingsDialog.tsx

Repository: kertal/git-vegas

Length of output: 368


🏁 Script executed:

rg -A 5 "type SettingsDialogProps" src/types

Repository: kertal/git-vegas

Length of output: 42


🏁 Script executed:

rg -n "SettingsDialogProps" src/types.ts -A 10

Repository: kertal/git-vegas

Length of output: 324


Add onClearReviewItems callback to SettingsDialog to ensure review cache clears with other caches.

Review items are stored separately but aren't being cleared when users clear their cache through the Settings dialog. The clearReviewItems function exists in the data fetching hook and is obtained in App.tsx (line 114), but it's never passed to SettingsDialog (lines 476-477). Users have no UI control to clear stale review items, which will persist across cache clears.

Add onClearReviewItems to the SettingsDialogProps interface, pass it from App.tsx alongside onClearEvents and onClearSearchItems, and invoke it when the user clears their cache.

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

In `@src/App.tsx` around lines 149 - 157, Add a new onClearReviewItems callback
prop to SettingsDialogProps and wire it through App.tsx: pass the existing
clearReviewItems function (from the data fetching hook) into the SettingsDialog
alongside onClearEvents and onClearSearchItems, and update the SettingsDialog
implementation to invoke onClearReviewItems when the user triggers "clear
cache." Locate the SettingsDialogProps type and the SettingsDialog component to
add the new prop, update the place in App.tsx where SettingsDialog is rendered
to include onClearReviewItems={clearReviewItems}, and ensure any cache-clear
handler inside SettingsDialog calls props.onClearReviewItems() in the same
sequence as onClearEvents and onClearSearchItems.

Add onClearReviewItems to SettingsDialogProps and invoke it alongside
onClearEvents and onClearSearchItems when the user triggers "Clear All"
in the settings dialog.

https://claude.ai/code/session_01C3KBpRv5F5LneNE8jKRDYN
Make the hook generic (`useIndexedDBStorage<T>`) so callers can specify
the actual stored type. Search and review stores now use `<GitHubItem>`
instead of forcing `GitHubEvent[]` and casting everywhere.

The two `as unknown as` casts are now confined to the hook's storage
boundary — all consumer code (data fetching, processing, views) is
type-safe end-to-end with no casts.

https://claude.ai/code/session_01C3KBpRv5F5LneNE8jKRDYN

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/hooks/useGitHubDataFetching.ts (1)

188-198: ⚠️ Potential issue | 🟡 Minor

Progress percentage will exceed 100% because onProgress is called multiple times per user.

onProgress increments currentProgress on every call, but it's compared against totalUsernames (i.e., usernames.length). Each user triggers onProgress ~4 times (search items, review items, events fetch, events count), so the percentage blows past 100% quickly.

Consider either tracking progress with a fixed total that accounts for all phases, or not incrementing a shared counter inside onProgress.

🐛 Possible fix — use phase-aware progress
       const totalUsernames = usernames.length;
+      const phasesPerUser = 4; // search, reviews, events-fetch, events-count
+      const totalSteps = totalUsernames * phasesPerUser;

       const onProgress = (message: string) => {
         currentProgress++;
         const progressPercent = Math.round(
-          (currentProgress / totalUsernames) * 100
+          (currentProgress / totalSteps) * 100
         );
         const prefix = hasExistingData ? 'Updating' : 'Fetching';
         setLoadingProgress(`${prefix} ${message} (${progressPercent}%)`);
       };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useGitHubDataFetching.ts` around lines 188 - 198, The progress
counter overflows because onProgress increments currentProgress against
totalUsernames but each username triggers multiple progress events; fix by
making progress phase-aware: define a constant STEPS_PER_USER (or compute
totalSteps) and set totalSteps = usernames.length * STEPS_PER_USER, then change
onProgress to increment currentProgress relative to totalSteps (or accept an
optional increment param) and compute progressPercent =
Math.round((currentProgress / totalSteps) * 100); update references to
currentProgress, totalUsernames, onProgress, setLoadingProgress and keep the
prefix logic with hasExistingData unchanged.
src/hooks/useIndexedDBStorage.ts (1)

70-82: ⚠️ Potential issue | 🟠 Major

clearEvents wipes the entire IndexedDB store, not just the current key.

eventsStorage.clear() delegates to indexedDBManager.clearAll(), which removes all entries from the EVENTS_STORE — not just the entry for the hook's key. The store() and retrieve() methods are key-scoped (storing data with id: key), but clear() indiscriminately clears the entire ObjectStore. With three separate useIndexedDBStorage instances using different keys (github-events-indexeddb, github-search-items-indexeddb, github-review-items-indexeddb), calling any single instance's clearEvents() nukes data from all three.

In useGitHubDataFetching.ts (lines 201-204), the "fresh search" path calls clearEvents(), clearSearchItems(), and clearReviewItems() sequentially — the first call already deletes everything, making the next two redundant. More critically, if any consumer calls clearReviewItems() in isolation (e.g., from SettingsDialog), it will also destroy events and search items.

Consider adding a key-scoped delete method to eventsStorage (e.g., eventsStorage.remove(key)) and using it in clearEvents instead of clear().

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

In `@src/hooks/useIndexedDBStorage.ts` around lines 70 - 82, The clearEvents
function currently calls eventsStorage.clear(), which invokes
indexedDBManager.clearAll() and wipes the entire EVENTS_STORE across all keys;
change this to remove only the current hook key by adding a key-scoped delete on
the storage layer (e.g., implement eventsStorage.remove(key) or
indexedDBManager.deleteById(id)) and then call that from useIndexedDBStorage's
clearEvents (use the hook's key value), updating clearEvents to await
eventsStorage.remove(key) and only reset local state after successful removal;
also ensure the new storage method mirrors the error handling currently around
eventsStorage.clear().
♻️ Duplicate comments (1)
src/App.tsx (1)

473-479: onClearReviewItems is now wired into SettingsDialog — prior concern resolved.

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

In `@src/App.tsx` around lines 473 - 479, SettingsDialog now receives the
onClearReviewItems prop but confirm that the handler clearReviewItems is
implemented and exported where defined and that SettingsDialog's propType/TS
interface includes onClearReviewItems; if missing, add onClearReviewItems: () =>
void to SettingsDialog's props and call the passed handler where the "clear
review items" control is activated; also remove the duplicate review comment
markers (duplicate_comment) from the PR description.
🧹 Nitpick comments (4)
src/hooks/useGitHubDataFetching.ts (3)

356-363: Storing review items unconditionally (even when empty) is a good pattern to clear stale data.

The comment accurately explains the intent. This ensures that if a user previously had review data but no longer does, the stale entries are replaced.

One nit: apiMode: 'search' is used for review items metadata. Consider using a distinct value (e.g., 'review') if the apiMode union is ever extended, to distinguish review stores from search stores in debugging/logging.

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

In `@src/hooks/useGitHubDataFetching.ts` around lines 356 - 363, The
storeReviewItems call that writes to 'github-review-items-indexeddb' currently
sets apiMode: 'search'—change that metadata to a distinct value like apiMode:
'review' to clearly identify this store as review-items (update the object
passed to storeReviewItems in useGitHubDataFetching.ts around the call that
includes lastFetch, usernames, startDate, endDate and apiMode); this keeps the
unconditional storage behavior but prevents review metadata from being conflated
with search-mode stores for debugging and future apiMode union expansions.

279-290: Review items are constructed with Record<string, unknown> spread — missing type narrowing.

reviewData.items is typed as any from the JSON response. The spread ...item plus explicit property assignments produces an object that looks like GitHubItem but has no compile-time validation. This is consistent with how searchItemsWithOriginal is built (line 239-244) and is a pre-existing pattern, but worth noting that neither code path validates the shape of the API response.

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

In `@src/hooks/useGitHubDataFetching.ts` around lines 279 - 290, The code uses an
untyped spread of reviewData.items into reviewItemsWithOriginal (currently typed
as Record<string, unknown>), which forfeits compile-time shape checks; replace
the anonymous Record usage by implementing a typed normalization: create or use
a GitHubItem interface/type and a narrow function (e.g.,
normalizeReviewItem(item: any): GitHubItem) or an inline explicit mapping that
reads required fields from item, validates/guards optional fields (assignee,
assignees) and returns a strongly typed object before adding original and
reviewedBy; update the map that builds reviewItemsWithOriginal to call that
normalizer (mirroring how searchItemsWithOriginal is handled) so the resulting
array is statically typed and safe.

261-304: Add delay between search and review fetch loops to avoid secondary rate limits.

The reviewed-by: search and pagination logic are correct and follow GitHub's Search API properly. However, there's a gap between the issues/PRs fetch loop (ending at line 259) and the review fetch loop (starting at line 262)—back-to-back Search API calls can trigger secondary rate limits. Add GITHUB_API_DELAY_MS pause between them.

The per_page=100 value is correct (GitHub Search API max), and the code properly tags items with reviewedBy metadata for downstream deduplication.

♻️ Add delay between search and review fetch
          onProgress(`Fetched ${totalSearchItems} issues/PRs for ${singleUsername}`);

+          // Small delay between search API calls to avoid secondary rate limits
+          await new Promise(resolve => setTimeout(resolve, GITHUB_API_DELAY_MS));
+
          // Fetch PRs reviewed by this user using the Search API (more accurate than Events API)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useGitHubDataFetching.ts` around lines 261 - 304, Between the
issues/PRs fetch loop and the review fetch loop insert an explicit delay using
GITHUB_API_DELAY_MS to avoid secondary Search API rate limits; specifically,
before starting the reviewQuery/reviewSearchPage while(true) loop (the block
that builds reviewQuery, reviewSearchPage and enters the loop that pushes into
allReviewItems and calls onProgress), add an await timeout (e.g., await new
Promise(resolve => setTimeout(resolve, GITHUB_API_DELAY_MS))) so there’s a pause
after the prior fetch loop completes and before review fetches begin.
src/hooks/useIndexedDBStorage.ts (1)

52-68: storeEvents accepts an arbitrary storageKey that can diverge from the hook's key.

The hook loads from key (line 32) but stores to storageKey (line 61). If a caller passes a different key to storeEvents, the local state (events, metadata) will be updated but a subsequent loadEvents / refreshEvents will re-read from the original key, silently losing the stored data. Consider either removing the storageKey parameter (always using key) or at minimum documenting this footgun.

♻️ Suggested simplification — use the hook's key internally
   const storeEvents = useCallback(async (
-    storageKey: string,
     newEvents: T[],
     newMetadata: EventsData['metadata']
   ) => {
     setError(null);
 
     try {
-      await eventsStorage.store(storageKey, newEvents as unknown as GitHubEvent[], newMetadata);
+      await eventsStorage.store(key, newEvents as unknown as GitHubEvent[], newMetadata);
       setEvents(newEvents);
       setMetadata(newMetadata);
     } catch (err) {
       console.error('Failed to store events:', err);
       setError(err instanceof Error ? err.message : 'Failed to store events');
     }
-  }, []);
+  }, [key]);

This would require updating all call sites to drop the first argument, but it eliminates an entire class of bugs.

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

In `@src/hooks/useIndexedDBStorage.ts` around lines 52 - 68, storeEvents currently
accepts a storageKey parameter that can diverge from the hook's internal key,
causing writes to a different ID than the hook reads (see storeEvents, key,
loadEvents, refreshEvents, and eventsStorage); fix by removing the storageKey
parameter and have storeEvents always use the hook's key when calling
eventsStorage.store and when updating state (update all call sites to drop the
first argument), or alternatively validate that storageKey === key and throw/log
if not — choose the former to eliminate the footgun.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/hooks/useGitHubDataFetching.ts`:
- Around line 188-198: The progress counter overflows because onProgress
increments currentProgress against totalUsernames but each username triggers
multiple progress events; fix by making progress phase-aware: define a constant
STEPS_PER_USER (or compute totalSteps) and set totalSteps = usernames.length *
STEPS_PER_USER, then change onProgress to increment currentProgress relative to
totalSteps (or accept an optional increment param) and compute progressPercent =
Math.round((currentProgress / totalSteps) * 100); update references to
currentProgress, totalUsernames, onProgress, setLoadingProgress and keep the
prefix logic with hasExistingData unchanged.

In `@src/hooks/useIndexedDBStorage.ts`:
- Around line 70-82: The clearEvents function currently calls
eventsStorage.clear(), which invokes indexedDBManager.clearAll() and wipes the
entire EVENTS_STORE across all keys; change this to remove only the current hook
key by adding a key-scoped delete on the storage layer (e.g., implement
eventsStorage.remove(key) or indexedDBManager.deleteById(id)) and then call that
from useIndexedDBStorage's clearEvents (use the hook's key value), updating
clearEvents to await eventsStorage.remove(key) and only reset local state after
successful removal; also ensure the new storage method mirrors the error
handling currently around eventsStorage.clear().

---

Duplicate comments:
In `@src/App.tsx`:
- Around line 473-479: SettingsDialog now receives the onClearReviewItems prop
but confirm that the handler clearReviewItems is implemented and exported where
defined and that SettingsDialog's propType/TS interface includes
onClearReviewItems; if missing, add onClearReviewItems: () => void to
SettingsDialog's props and call the passed handler where the "clear review
items" control is activated; also remove the duplicate review comment markers
(duplicate_comment) from the PR description.

---

Nitpick comments:
In `@src/hooks/useGitHubDataFetching.ts`:
- Around line 356-363: The storeReviewItems call that writes to
'github-review-items-indexeddb' currently sets apiMode: 'search'—change that
metadata to a distinct value like apiMode: 'review' to clearly identify this
store as review-items (update the object passed to storeReviewItems in
useGitHubDataFetching.ts around the call that includes lastFetch, usernames,
startDate, endDate and apiMode); this keeps the unconditional storage behavior
but prevents review metadata from being conflated with search-mode stores for
debugging and future apiMode union expansions.
- Around line 279-290: The code uses an untyped spread of reviewData.items into
reviewItemsWithOriginal (currently typed as Record<string, unknown>), which
forfeits compile-time shape checks; replace the anonymous Record usage by
implementing a typed normalization: create or use a GitHubItem interface/type
and a narrow function (e.g., normalizeReviewItem(item: any): GitHubItem) or an
inline explicit mapping that reads required fields from item, validates/guards
optional fields (assignee, assignees) and returns a strongly typed object before
adding original and reviewedBy; update the map that builds
reviewItemsWithOriginal to call that normalizer (mirroring how
searchItemsWithOriginal is handled) so the resulting array is statically typed
and safe.
- Around line 261-304: Between the issues/PRs fetch loop and the review fetch
loop insert an explicit delay using GITHUB_API_DELAY_MS to avoid secondary
Search API rate limits; specifically, before starting the
reviewQuery/reviewSearchPage while(true) loop (the block that builds
reviewQuery, reviewSearchPage and enters the loop that pushes into
allReviewItems and calls onProgress), add an await timeout (e.g., await new
Promise(resolve => setTimeout(resolve, GITHUB_API_DELAY_MS))) so there’s a pause
after the prior fetch loop completes and before review fetches begin.

In `@src/hooks/useIndexedDBStorage.ts`:
- Around line 52-68: storeEvents currently accepts a storageKey parameter that
can diverge from the hook's internal key, causing writes to a different ID than
the hook reads (see storeEvents, key, loadEvents, refreshEvents, and
eventsStorage); fix by removing the storageKey parameter and have storeEvents
always use the hook's key when calling eventsStorage.store and when updating
state (update all call sites to drop the first argument), or alternatively
validate that storageKey === key and throw/log if not — choose the former to
eliminate the footgun.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0570629 and 637f2ee.

📒 Files selected for processing (4)
  • src/App.tsx
  • src/hooks/useGitHubDataFetching.ts
  • src/hooks/useGitHubDataProcessing.ts
  • src/hooks/useIndexedDBStorage.ts

The REST Search API `reviewed-by:` query filters by PR `updated` date,
not when the review was submitted. Add GraphQL-based enrichment that
fetches PullRequestReview timeline items to get the real `createdAt`
for each reviewer, stored as `reviewed_at` on GitHubItem.

Changes:
- Add `reviewed_at` field to GitHubItem type
- New `src/utils/reviewDates.ts`: batched GraphQL queries to fetch
  review submission dates per reviewer per PR
- Enrich review items with dates during fetch (useGitHubDataFetching)
- Use `reviewed_at` for date filtering in categorizeRawSearchItems
- Display `reviewed_at` in ItemRow (desktop + mobile) and Summary

https://claude.ai/code/session_01C3KBpRv5F5LneNE8jKRDYN

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

🧹 Nitpick comments (6)
src/components/ItemRow.tsx (1)

154-168: Duplicated avatar logic across desktop and mobile layouts.

The reviewer avatar rendering is copy-pasted between desktop (lines 154–168) and mobile (lines 360–373). This mirrors the pre-existing duplication pattern for the assignee branch, so it's not new debt from this PR. Consider extracting a shared <ItemAvatars item={item} size={...} /> component in a follow-up to reduce the duplicated surface.

Also applies to: 360-373

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

In `@src/components/ItemRow.tsx` around lines 154 - 168, The reviewer points out
duplicated avatar rendering logic for reviews between desktop and mobile;
extract the duplicated JSX into a shared component (e.g., create an ItemAvatars
component) and replace both desktop and mobile branches with <ItemAvatars
item={item} size={size} /> or similar; ensure ItemAvatars encapsulates the
hasDistinctReviewer(item) / hasDistinctAssignee(item) checks and returns the
correct AvatarStack / Avatar outputs (using the same props currently used in the
Avatar and AvatarStack elements) so both layouts reuse the single
implementation.
src/hooks/useGitHubDataFetching.ts (2)

286-290: Constructed avatar URL uses GitHub's .png redirect — pragmatic but may differ from actual avatar.

https://github.com/${singleUsername}.png works for public profiles but will differ from the actual avatar_url returned by the API (which includes a hash parameter for cache-busting). This could cause duplicate avatar image loads if the same user's avatar is rendered elsewhere with the API-sourced URL. Minor cosmetic concern — no functional impact.

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

In `@src/hooks/useGitHubDataFetching.ts` around lines 286 - 290, The constructed
avatar URL for reviewedBy uses the simplistic
https://github.com/${singleUsername}.png which can differ from the real
API-provided avatar_url and cause duplicate/uncached images; update the logic in
useGitHubDataFetching.ts so reviewedBy.avatar_url is sourced from the GitHub API
response (prefer the reviewer/user object's avatar_url if available) or, if not
present, fetch the user object via the GitHub users endpoint for singleUsername
and use its avatar_url (avoid hardcoded .png redirects and preserve any
query/hash params for cache-busting).

192-199: onProgress counter inflates well past 100%.

currentProgress is incremented on every onProgress(...) call (lines 260, 305, 310, plus internal calls from fetchAllEvents), but the percentage is calculated against totalUsernames. For a single username, this reaches 300%+ before events are even fully fetched. This is a pre-existing issue, but the new review-fetch progress call on line 305 adds to it.

Consider tracking progress as discrete stages (e.g., Fetching issues for user 1/2..., Fetching reviews for user 1/2...) rather than a percentage, or properly calculating totalSteps = totalUsernames * stepsPerUser.

Also applies to: 260-260, 305-305

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

In `@src/hooks/useGitHubDataFetching.ts` around lines 192 - 199, The onProgress
handler currently increments currentProgress per call and computes percent
against totalUsernames causing >100% values; fix by computing a proper
totalSteps and using it for percent (e.g., totalSteps = totalUsernames *
stepsPerUser where stepsPerUser counts the discrete operations per user like
fetch issues, fetch reviews, fetch events), then replace progressPercent =
Math.round((currentProgress / totalUsernames) * 100) with
Math.round((currentProgress / totalSteps) * 100). Update all places that call
onProgress (the onProgress definition, the fetchAllEvents calls, and the new
review-fetch progress call locations) to increment currentProgress once per
logical step, or alternatively change onProgress to accept a stepCount delta and
increment by that; keep the existing prefix logic (hasExistingData ? 'Updating'
: 'Fetching') when building setLoadingProgress.
src/utils/reviewDates.ts (2)

160-184: Progress callback only fires at 0% and 100%.

onProgress is called with (0, items.length) and then (items.length, items.length) with nothing in between. If there are many batches, the caller has no visibility into intermediate progress. Consider invoking onProgress after each batch completes inside fetchReviewDates.


47-65: GraphQL injection risk: use parameterized queries instead of string interpolation.

pr.owner and pr.repo are interpolated directly into the GraphQL query string. While these values originate from GitHub API responses and are normally safe, this pattern is fragile — a crafted html_url in IndexedDB could inject arbitrary GraphQL. The regex capture [^/]+ permits characters like " and \ that would break or manipulate the query.

Consider using GraphQL variables or at minimum sanitizing/validating the owner and repo values against ^[a-zA-Z0-9._-]+$.

Minimal mitigation: validate owner/repo before interpolation
+const SAFE_NAME_RE = /^[a-zA-Z0-9._-]+$/;
+
 const buildBatchQuery = (
   prs: { owner: string; repo: string; number: number }[]
 ): string => {
-  const fragments = prs.map((pr, i) =>
+  const safePrs = prs.filter(pr => SAFE_NAME_RE.test(pr.owner) && SAFE_NAME_RE.test(pr.repo));
+  const fragments = safePrs.map((pr, i) =>
     `pr${i}: repository(owner: "${pr.owner}", name: "${pr.repo}") {

Note: The ideal fix is to restructure the query to use GraphQL variables, but that's harder with batched aliases. The validation approach is a pragmatic safeguard.

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

In `@src/utils/reviewDates.ts` around lines 47 - 65, The buildBatchQuery function
interpolates pr.owner and pr.repo directly into the GraphQL string which enables
injection; validate or sanitize these values before building the query (e.g.,
enforce a strict whitelist regex like ^[A-Za-z0-9._-]+$ on pr.owner and pr.repo,
or reject/filter any PR entries that fail validation) and throw or skip invalid
entries so only safe identifiers are interpolated; update the callers that pass
prs to ensure they handle/recover from rejections and document that
buildBatchQuery will only accept validated owner/repo values.
src/views/Summary.tsx (1)

62-68: Review detection heuristic is reasonable but may over-match.

Three conditions are OR'd to detect reviews: reviewedBy presence, title prefix "Review on:", and originalEventType === 'PullRequestReviewEvent'. The title-prefix check is fragile — if a PR is genuinely titled starting with "Review on:", it would be misclassified. That said, this is a low-probability edge case and the impact is only cosmetic (different grouping key).

Worth adding a brief inline comment noting the title-prefix check is a legacy heuristic for items sourced from the Events API.

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

In `@src/views/Summary.tsx` around lines 62 - 68, Add an inline comment above the
isReview heuristic explaining that the title-prefix check
(item.title.startsWith('Review on:')) is a legacy heuristic used for Events
API-sourced items and may over-match if a PR is legitimately titled that way;
reference the isReview variable, the reviewedBy check, the
title.startsWith('Review on:') condition, and originalEventType ===
'PullRequestReviewEvent' so future readers know why the fragile title check
exists and that its impact is only cosmetic (affects groupingKey).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/components/ItemRow.tsx`:
- Around line 154-168: The reviewer points out duplicated avatar rendering logic
for reviews between desktop and mobile; extract the duplicated JSX into a shared
component (e.g., create an ItemAvatars component) and replace both desktop and
mobile branches with <ItemAvatars item={item} size={size} /> or similar; ensure
ItemAvatars encapsulates the hasDistinctReviewer(item) /
hasDistinctAssignee(item) checks and returns the correct AvatarStack / Avatar
outputs (using the same props currently used in the Avatar and AvatarStack
elements) so both layouts reuse the single implementation.

In `@src/hooks/useGitHubDataFetching.ts`:
- Around line 286-290: The constructed avatar URL for reviewedBy uses the
simplistic https://github.com/${singleUsername}.png which can differ from the
real API-provided avatar_url and cause duplicate/uncached images; update the
logic in useGitHubDataFetching.ts so reviewedBy.avatar_url is sourced from the
GitHub API response (prefer the reviewer/user object's avatar_url if available)
or, if not present, fetch the user object via the GitHub users endpoint for
singleUsername and use its avatar_url (avoid hardcoded .png redirects and
preserve any query/hash params for cache-busting).
- Around line 192-199: The onProgress handler currently increments
currentProgress per call and computes percent against totalUsernames causing
>100% values; fix by computing a proper totalSteps and using it for percent
(e.g., totalSteps = totalUsernames * stepsPerUser where stepsPerUser counts the
discrete operations per user like fetch issues, fetch reviews, fetch events),
then replace progressPercent = Math.round((currentProgress / totalUsernames) *
100) with Math.round((currentProgress / totalSteps) * 100). Update all places
that call onProgress (the onProgress definition, the fetchAllEvents calls, and
the new review-fetch progress call locations) to increment currentProgress once
per logical step, or alternatively change onProgress to accept a stepCount delta
and increment by that; keep the existing prefix logic (hasExistingData ?
'Updating' : 'Fetching') when building setLoadingProgress.

In `@src/utils/reviewDates.ts`:
- Around line 47-65: The buildBatchQuery function interpolates pr.owner and
pr.repo directly into the GraphQL string which enables injection; validate or
sanitize these values before building the query (e.g., enforce a strict
whitelist regex like ^[A-Za-z0-9._-]+$ on pr.owner and pr.repo, or reject/filter
any PR entries that fail validation) and throw or skip invalid entries so only
safe identifiers are interpolated; update the callers that pass prs to ensure
they handle/recover from rejections and document that buildBatchQuery will only
accept validated owner/repo values.

In `@src/views/Summary.tsx`:
- Around line 62-68: Add an inline comment above the isReview heuristic
explaining that the title-prefix check (item.title.startsWith('Review on:')) is
a legacy heuristic used for Events API-sourced items and may over-match if a PR
is legitimately titled that way; reference the isReview variable, the reviewedBy
check, the title.startsWith('Review on:') condition, and originalEventType ===
'PullRequestReviewEvent' so future readers know why the fragile title check
exists and that its impact is only cosmetic (affects groupingKey).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 637f2ee and d0ab04d.

📒 Files selected for processing (6)
  • src/components/ItemRow.tsx
  • src/hooks/useGitHubDataFetching.ts
  • src/types.ts
  • src/utils/rawDataUtils.ts
  • src/utils/reviewDates.ts
  • src/views/Summary.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/utils/rawDataUtils.ts

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.

3 participants