Activity timeline, repo shell, and GitHub helpers#151
Conversation
- Issue/PR detail activity timelines, grouped label events, close reasons - Comment reaction bar; sidebar section titleRight for contributors count - Repo activity card empty states; header star/fork actions - Extend github.functions/types and icons for new UI
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 9 minutes and 10 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (13)
📝 WalkthroughWalkthroughAdds comment reactions, issue state close/reopen with reasons, repo star/fork actions, grouped timeline state-toggle events, GraphQL/REST mapping for reactions and forks, new server mutations, types for reactions/forks/close reasons, and UI updates across issue, pull, and repo components. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Component as IssueCommentReactionBar<br/>(React)
participant Cache as Query Cache
participant API as toggleIssueCommentReaction<br/>(Server)
participant Toast as Toast / PermissionCheck
User->>Component: Click reaction chip
Component->>Component: Determine add vs remove<br/>and guard single-flight
Note over Component,Cache: Apply optimistic update
Component->>Cache: Patch reactions & viewer state for<br/>issue/pull query keys
Component->>API: Call toggleIssueCommentReaction(owner,repo,issue,comment,content,remove)
alt OK
API-->>Component: Success
else Error / non-OK
API-->>Component: Error / non-OK
Component->>Cache: Rollback snapshot
Component->>Toast: Show error toast
Component->>Toast: Call checkPermissionWarning (if non-OK)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| 🔵 In progress View logs |
diffkit | d597259 | Apr 18 2026, 02:57 AM |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/dashboard/src/lib/github.functions.ts (2)
3665-3668:⚠️ Potential issue | 🟠 MajorBump these cache resource versions before shipping the new payload shape.
These cached resources now serialize new fields (
graphqlId,reactions, fork-parent metadata, correctedviewerHasStarred), but the keys stay on.v1. Existing D1 entries will keep serving the old shape after deploy, which can leave reaction toggles without acommentGraphqlIduntil expiry.Suggested fix
- resource: "pulls.pageData.graphql.v1", + resource: "pulls.pageData.graphql.v2", - resource: "issues.pageData.graphql.v1", + resource: "issues.pageData.graphql.v2", - resource: "repo.overview.v1", + resource: "repo.overview.v2",Also applies to: 3993-3996, 7380-7383
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/lib/github.functions.ts` around lines 3665 - 3668, The cache resource keys that serialize the new fields are still using the old version suffix ("pulls.pageData.graphql.v1"); update/bump these resource version strings (e.g., change "pulls.pageData.graphql.v1" to "pulls.pageData.graphql.v2") wherever they are defined so existing D1 entries with the old shape won’t be served after deploy—apply the same bump to the other matching cached resource declarations called out in the review (the other occurrences of the "pulls.pageData.graphql.v1" resource).
3672-3795:⚠️ Potential issue | 🟠 MajorUse user-scoped context to resolve viewer in GraphQL page fetchers.
getGitHubContextForRepository()returns installation-scoped Octokit for app-installed repos, butgetViewer()callsrest.users.getAuthenticated(), which only works with user-scoped tokens. This causes the Promise.all to reject entirely, preventing GraphQL queries from succeeding and forcing fallback to REST, which loses the newgraphqlIdand reaction data.Use
getGitHubUserContextForRepository()instead, and make viewer nullable since the function already supports undefinedviewerLogin.Suggested fix
- const [response, timelineResult, viewer] = await Promise.all([ + const viewerPromise = getGitHubUserContextForRepository(data).then( + (userContext) => (userContext ? getViewer(userContext) : null), + ); + const [response, timelineResult, viewer] = await Promise.all([ // ... - getViewer(context), + viewerPromise, ]);- comments: mapGraphQLComments(viewer.login, pull.firstComments, pull.lastComments), + comments: mapGraphQLComments( + viewer?.login, + pull.firstComments, + pull.lastComments, + ),Apply the same change in the issue page fetcher (lines ~4000–4088).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/lib/github.functions.ts` around lines 3672 - 3795, The problem is that getViewer() is being called with an installation-scoped context (from getGitHubContextForRepository) which fails for user-only endpoints; replace the installation-scoped context with a user-scoped one by calling getGitHubUserContextForRepository(...) where the context for the viewer is created, call getViewer using that user-scoped context, and allow the viewer to be nullable (viewer can be undefined) when passed to downstream functions; update the same change in the issue page fetcher as well. Ensure you modify the Promise.all inputs around executeGitHubGraphQL, getTimelineEventsResult, and getViewer to use getGitHubUserContextForRepository and handle undefined viewerLogin where used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/dashboard/src/components/details/comment-reaction-bar.tsx`:
- Around line 175-186: The zero-count reaction button is fully hidden for
non-hover users, making first-time reactions unreachable on touch devices; keep
it reachable by showing it on devices that lack hover: add a small utility CSS
rule (e.g. .show-on-touch { `@media` (hover: none) { display: inline-flex
!important } }) in a stylesheet, then update the conditional classes on the
button in comment-reaction-bar.tsx (the button with onClick={() => void
handleToggle(content)}) to include that utility alongside the existing "hidden
group-hover/comment:inline-flex group-focus-within/comment:inline-flex" so
zero-count buttons remain visible on touch devices while still hiding for
pointer-hover UIs.
In `@apps/dashboard/src/components/details/detail-activity.tsx`:
- Around line 190-204: The current flow may partially apply actions because
createComment() is awaited but its result is ignored and setValue("") is called
before updateIssueState(); ensure both writes succeed atomically by first
awaiting createComment() and checking for errors, only clearing the editor
(setValue("")) after createComment() succeeds, and only calling
updateIssueState() if createComment() succeeded (handle and propagate
createComment errors to avoid proceeding); similarly, if updateIssueState()
fails, surface the error and avoid clearing or roll back UI state as
appropriate—update the logic around createComment, setValue, and
updateIssueState to gate the second write on the success of the first and
surface failures to the caller/user.
In `@apps/dashboard/src/components/repo/repo-header.tsx`:
- Around line 51-60: The owner and repo name text nodes are inline so long names
can overflow; update the Link rendering repo.owner and the span rendering
repo.name in RepoHeader (the elements using {repo.owner} and {repo.name}) to be
shrinkable by adding a block/inline-block display and min-w-0 (e.g., className
includes "min-w-0 block" or "min-w-0 inline-block") so they properly ellipsize
inside the parent flex container; apply the same change to the second occurrence
later in the file (the other Link/{repo.owner} and truncate span pair around
lines 103-111).
In `@apps/dashboard/src/components/repo/repo-sidebar.tsx`:
- Around line 138-143: The "+ more" is rendered as a focusable button with no
action; change this to a non-interactive element (for example replace the
<button> used in repo-sidebar.tsx with a <span> or <div> styled the same) so it
is not focusable, or if it should expand/navigate implement an onClick handler
and proper ARIA (e.g., call the expand handler and set aria-expanded) on that
element; locate the markup rendering "+ {formatCount(data.totalCount -
data.contributors.length)} more" and either swap the element to a plain text
node or hook it up to the appropriate expand/navigation function.
In `@apps/dashboard/src/lib/github.functions.ts`:
- Around line 3813-3817: The paginated comment pages are still returning the old
REST shape (via getCommentsPageResult) so subsequent pages lack graphqlId and
reactions and fail to render/toggle; update getCommentsPageResult (or the
pagination path used by getCommentPage) to include graphqlId and reactions for
each comment so mapGraphQLComments (used with viewer.login and
pull.firstComments / pull.lastComments) receives the same shape as the initial
GraphQL payload, or add a conversion step inside getCommentPage to map the REST
comment shape to the GraphQL-shaped comment (including graphqlId and reactions)
before calling mapGraphQLComments — ensure the same transformation is applied
for the other occurrence around the 4103–4107 block as well.
- Around line 5540-5554: The current change only busts the issue entity cache
(githubRevalidationSignalKeys.issueEntity) after an issue state change; update
the Promise.all to also invalidate list/overview caches so lists and
openIssueCount stay fresh — call bustIssueCaches or bumpGitHubCacheNamespaces
for the repo/list keys (e.g., githubRevalidationSignalKeys.issuesFromRepo or
issuesForRepo, githubRevalidationSignalKeys.issuesFromUser, and
githubRevalidationSignalKeys.repoOverview) with the same owner/repo (and user
where applicable) payloads so getIssuesFromRepo, getIssuesFromUser and
getRepoOverview are revalidated when an issue is closed/reopened. Ensure the new
keys are added alongside the existing issueEntity entry in the Promise.all call.
- Around line 6278-6283: After a successful fork only the new fork's cache is
being bumped; also invalidate the parent/source repo meta so the original repo's
forkCount updates. After the existing bumpGitHubCacheNamespaces call (which uses
response.data.owner.login and response.data.name), add another
bumpGitHubCacheNamespaces call for the parent/source using response.data.parent
(or response.data.source) and pass githubRevalidationSignalKeys.repoMeta({
owner: response.data.parent.owner.login, repo: response.data.parent.name })
(guarding that response.data.parent exists before calling) so the original
repository's cached overview is revalidated.
---
Outside diff comments:
In `@apps/dashboard/src/lib/github.functions.ts`:
- Around line 3665-3668: The cache resource keys that serialize the new fields
are still using the old version suffix ("pulls.pageData.graphql.v1");
update/bump these resource version strings (e.g., change
"pulls.pageData.graphql.v1" to "pulls.pageData.graphql.v2") wherever they are
defined so existing D1 entries with the old shape won’t be served after
deploy—apply the same bump to the other matching cached resource declarations
called out in the review (the other occurrences of the
"pulls.pageData.graphql.v1" resource).
- Around line 3672-3795: The problem is that getViewer() is being called with an
installation-scoped context (from getGitHubContextForRepository) which fails for
user-only endpoints; replace the installation-scoped context with a user-scoped
one by calling getGitHubUserContextForRepository(...) where the context for the
viewer is created, call getViewer using that user-scoped context, and allow the
viewer to be nullable (viewer can be undefined) when passed to downstream
functions; update the same change in the issue page fetcher as well. Ensure you
modify the Promise.all inputs around executeGitHubGraphQL,
getTimelineEventsResult, and getViewer to use getGitHubUserContextForRepository
and handle undefined viewerLogin where used.
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: b67ceb52-2958-4f66-83fb-1df66e752d4a
📒 Files selected for processing (16)
apps/dashboard/src/components/details/comment-reaction-bar.tsxapps/dashboard/src/components/details/detail-activity.tsxapps/dashboard/src/components/details/detail-sidebar.tsxapps/dashboard/src/components/details/grouped-label-event.tsxapps/dashboard/src/components/issues/detail/issue-detail-activity.tsxapps/dashboard/src/components/issues/detail/issue-detail-page.tsxapps/dashboard/src/components/pulls/detail/pull-detail-activity.tsxapps/dashboard/src/components/repo/repo-activity-cards.tsxapps/dashboard/src/components/repo/repo-header.tsxapps/dashboard/src/components/repo/repo-overview-page.tsxapps/dashboard/src/components/repo/repo-sidebar.tsxapps/dashboard/src/components/repo/repo-star-fork-actions.tsxapps/dashboard/src/lib/github.functions.tsapps/dashboard/src/lib/github.types.tsapps/dashboard/src/lib/timeline-close-reason.tspackages/icons/src/index.ts
| <button | ||
| key={content} | ||
| type="button" | ||
| onClick={() => void handleToggle(content)} | ||
| className={cn( | ||
| "inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs transition-colors", | ||
| active | ||
| ? "border-accent-foreground/30 bg-accent/15 text-foreground" | ||
| : "border-transparent bg-surface-1 text-muted-foreground hover:bg-surface-2 hover:text-foreground", | ||
| count === 0 && | ||
| "hidden group-hover/comment:inline-flex group-focus-within/comment:inline-flex", | ||
| )} |
There was a problem hiding this comment.
Keep first-time reactions reachable on touch devices.
Zero-count buttons are fully hidden until hover/focus-within. On touch screens there is no hover, so a comment with no existing reactions has no visible way to add the first one. Please leave an always-reachable affordance here for coarse-pointer/mobile users.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/components/details/comment-reaction-bar.tsx` around lines
175 - 186, The zero-count reaction button is fully hidden for non-hover users,
making first-time reactions unreachable on touch devices; keep it reachable by
showing it on devices that lack hover: add a small utility CSS rule (e.g.
.show-on-touch { `@media` (hover: none) { display: inline-flex !important } }) in
a stylesheet, then update the conditional classes on the button in
comment-reaction-bar.tsx (the button with onClick={() => void
handleToggle(content)}) to include that utility alongside the existing "hidden
group-hover/comment:inline-flex group-focus-within/comment:inline-flex" so
zero-count buttons remain visible on touch devices while still hiding for
pointer-hover UIs.
| const userId = context.session.user.id; | ||
| await Promise.all([ | ||
| bustIssueCaches(userId, { | ||
| owner: data.owner, | ||
| repo: data.repo, | ||
| issueNumber: data.issueNumber, | ||
| }), | ||
| bumpGitHubCacheNamespaces([ | ||
| githubRevalidationSignalKeys.issueEntity({ | ||
| owner: data.owner, | ||
| repo: data.repo, | ||
| issueNumber: data.issueNumber, | ||
| }), | ||
| ]), | ||
| ]); |
There was a problem hiding this comment.
Invalidate repo/list caches on issue state changes too.
Closing or reopening an issue changes list membership and openIssueCount, but this only busts the entity/detail caches. getIssuesFromRepo, getIssuesFromUser, and getRepoOverview can keep serving stale data until TTL expiry.
Suggested fix
await Promise.all([
bustIssueCaches(userId, {
owner: data.owner,
repo: data.repo,
issueNumber: data.issueNumber,
}),
bumpGitHubCacheNamespaces([
+ githubRevalidationSignalKeys.issuesMine,
+ githubRevalidationSignalKeys.repoMeta({
+ owner: data.owner,
+ repo: data.repo,
+ }),
githubRevalidationSignalKeys.issueEntity({
owner: data.owner,
repo: data.repo,
issueNumber: data.issueNumber,
}),
]),
]);📝 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 userId = context.session.user.id; | |
| await Promise.all([ | |
| bustIssueCaches(userId, { | |
| owner: data.owner, | |
| repo: data.repo, | |
| issueNumber: data.issueNumber, | |
| }), | |
| bumpGitHubCacheNamespaces([ | |
| githubRevalidationSignalKeys.issueEntity({ | |
| owner: data.owner, | |
| repo: data.repo, | |
| issueNumber: data.issueNumber, | |
| }), | |
| ]), | |
| ]); | |
| const userId = context.session.user.id; | |
| await Promise.all([ | |
| bustIssueCaches(userId, { | |
| owner: data.owner, | |
| repo: data.repo, | |
| issueNumber: data.issueNumber, | |
| }), | |
| bumpGitHubCacheNamespaces([ | |
| githubRevalidationSignalKeys.issuesMine, | |
| githubRevalidationSignalKeys.repoMeta({ | |
| owner: data.owner, | |
| repo: data.repo, | |
| }), | |
| githubRevalidationSignalKeys.issueEntity({ | |
| owner: data.owner, | |
| repo: data.repo, | |
| issueNumber: data.issueNumber, | |
| }), | |
| ]), | |
| ]); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/lib/github.functions.ts` around lines 5540 - 5554, The
current change only busts the issue entity cache
(githubRevalidationSignalKeys.issueEntity) after an issue state change; update
the Promise.all to also invalidate list/overview caches so lists and
openIssueCount stay fresh — call bustIssueCaches or bumpGitHubCacheNamespaces
for the repo/list keys (e.g., githubRevalidationSignalKeys.issuesFromRepo or
issuesForRepo, githubRevalidationSignalKeys.issuesFromUser, and
githubRevalidationSignalKeys.repoOverview) with the same owner/repo (and user
where applicable) payloads so getIssuesFromRepo, getIssuesFromUser and
getRepoOverview are revalidated when an issue is closed/reopened. Ensure the new
keys are added alongside the existing issueEntity entry in the Promise.all call.
- Store userLoginsByContent from GraphQL; optimistic patch with viewerLogin - Tooltip lists reactors (first 10 +N); pass viewerLogin from issue/PR bubbles - Motion layout, AnimatePresence, NumberFlow for reaction chips
- Bump GraphQL page and repo overview cache resource versions to v2 - Resolve viewer via getGitHubUserContextForRepository for issue/PR page loads - Map REST comment node_id to graphqlId for paginated comments and pull comments - Invalidate repoMeta on issue state change; bump parent repoMeta after fork - Gate close/reopen on successful createComment; repo header/sidebar a11y - Show reaction chips on coarse pointers (hover: none) without hover
There was a problem hiding this comment.
🧹 Nitpick comments (1)
apps/dashboard/src/components/details/comment-reaction-bar.tsx (1)
252-316: PrefermapoverflatMapwhen returning single-element arrays.Using
flatMapwith a return of[element]is semantically equivalent tomapbut adds cognitive overhead. Since each iteration returns exactly one element,mapis more idiomatic.♻️ Suggested simplification
- {orderedReactions.flatMap(({ content, emoji }) => { + {orderedReactions.map(({ content, emoji }) => { const count = counts[content] ?? 0; const active = reactions?.viewerReacted.includes(content) ?? false; const tooltipText = reactionActorTooltipText( reactions?.userLoginsByContent?.[content], count, ); const chip = ( // ... chip JSX unchanged ... ); - return [ - count > 0 ? ( - <Tooltip key={content} delayDuration={300}> - <TooltipTrigger asChild>{chip}</TooltipTrigger> - <TooltipContent - side="top" - className="max-w-xs text-xs leading-snug" - > - {tooltipText} - </TooltipContent> - </Tooltip> - ) : ( - <Fragment key={content}>{chip}</Fragment> - ), - ]; + return count > 0 ? ( + <Tooltip key={content} delayDuration={300}> + <TooltipTrigger asChild>{chip}</TooltipTrigger> + <TooltipContent + side="top" + className="max-w-xs text-xs leading-snug" + > + {tooltipText} + </TooltipContent> + </Tooltip> + ) : ( + <Fragment key={content}>{chip}</Fragment> + ); })}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/details/comment-reaction-bar.tsx` around lines 252 - 316, The code uses orderedReactions.flatMap(...) but each iteration returns a single-element array; replace flatMap with map on orderedReactions and return the element directly (remove the surrounding [ ... ]), keeping the same key on the Tooltip/Fragment and preserving behavior of tooltipText (from reactionActorTooltipText), handleToggle click, NumberFlow count, and the Tooltip/TooltipTrigger/TooltipContent branches; this simplifies the iteration while keeping keys and aria attributes unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@apps/dashboard/src/components/details/comment-reaction-bar.tsx`:
- Around line 252-316: The code uses orderedReactions.flatMap(...) but each
iteration returns a single-element array; replace flatMap with map on
orderedReactions and return the element directly (remove the surrounding [ ...
]), keeping the same key on the Tooltip/Fragment and preserving behavior of
tooltipText (from reactionActorTooltipText), handleToggle click, NumberFlow
count, and the Tooltip/TooltipTrigger/TooltipContent branches; this simplifies
the iteration while keeping keys and aria attributes unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 81d6d47f-6163-4710-a9b3-3e73b42f3fbb
📒 Files selected for processing (5)
apps/dashboard/src/components/details/comment-reaction-bar.tsxapps/dashboard/src/components/issues/detail/issue-detail-activity.tsxapps/dashboard/src/components/pulls/detail/pull-detail-activity.tsxapps/dashboard/src/lib/github.functions.tsapps/dashboard/src/lib/github.types.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- apps/dashboard/src/lib/github.types.ts
- apps/dashboard/src/components/issues/detail/issue-detail-activity.tsx
- apps/dashboard/src/components/pulls/detail/pull-detail-activity.tsx
- apps/dashboard/src/lib/github.functions.ts
Resolve detail-activity: combine comment media upload (main) with issue close/reopen controls (branch).
Fetch GraphQL id and reactions on issue/PR detail, optimistic detail patching in the reaction bar, and inline layout below markdown.
Summary
pt-6/pb-8).github.functions,github.types, and icons to support the above.Summary by CodeRabbit
New Features
UI Improvements