Skip to content

feat(forge): Pull Requests sidebar section + branch fork icon#5

Merged
jcardonne merged 2 commits into
mainfrom
feat/pull-requests
Jul 5, 2026
Merged

feat(forge): Pull Requests sidebar section + branch fork icon#5
jcardonne merged 2 commits into
mainfrom
feat/pull-requests

Conversation

@jcardonne

@jcardonne jcardonne commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

Adds pull/merge request awareness to the app.

  • Sidebar "Pull Requests" section — rich rows: author avatar, #number, CI status dot (green/red/amber), review mark (approved ✓ / changes-requested ✗), title, draft tag. Click opens the PR/MR in the browser; right-click copies its URL. Manual refresh button.
  • Graph badge fork icon — a branch with an open PR/MR shows the fork glyph instead of the monitor; its right-click menu gains "Open pull request #N".
  • Backendforge::list_prs via gh/glab CLI (normalized struct), open_url command (http/https-guarded).

Notes

  • GitHub = fully rich. GitLab is degraded: glab mr list JSON lacks CI-rollup / review-decision / avatar, so those show none/initials for GitLab. Title/number/branch/draft/author still work.
  • PRs are fetched on repo load / provider change + manual refresh only — never on the hot refresh() path (it's a network CLI call).
  • Non-forge remotes → empty list.

Test

  • Rust: cargo test (new rollup_checks + normalize_review tests; 64 pass). Verified gh pr list --json shape against a live repo matches the serde structs.
  • Frontend: tsc clean, vitest 56/56, vite build ok.

Summary by CodeRabbit

  • New Features

    • Added a Pull Requests section with open PR/MR listings, including branch, author, draft state, review status, and CI indicators.
    • Branches with open PRs are now marked more clearly in the graph and branch menus.
    • Added actions to open a pull request in the browser and refresh pull request data.
  • Bug Fixes

    • Restricted web-link opening to safe http and https URLs only.
    • Improved pull request status handling so CI and review states display consistently across providers.

- Backend `list_prs` (gh/glab CLI) returns normalized open PRs/MRs: number,
  title, url, source branch, draft, author (+avatar), rolled-up CI status, and
  review decision. GitHub is fully rich; GitLab is degraded (no CI/review/avatar
  from `glab mr list`). New `open_url` command opens a PR in the browser
  (guarded to http/https).
- Sidebar "Pull Requests" section: rich rows (author avatar, #number, CI dot,
  review mark, title, draft tag). Click opens in browser; right-click copies the
  URL. Manual refresh button (a network CLI call, off the hot path).
- Graph: a branch badge whose branch has an open PR/MR shows the fork icon
  instead of the monitor; its right-click menu gains "Open pull request #N".

Fetched on repo load / provider change + manual refresh only, never after every
git op. Non-forge remotes yield an empty list (section just shows empty).
@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jcardonne, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 51 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 43191fdb-0bb7-4ef3-b610-ab3f5f5a8466

📥 Commits

Reviewing files that changed from the base of the PR and between da5ea43 and 3bbcf83.

📒 Files selected for processing (1)
  • src/components/GraphView.tsx

Walkthrough

Adds pull/merge request listing support: a Rust forge module fetches and normalizes GitHub/GitLab PR data via gh/glab CLIs, new Tauri commands (list_prs, open_url) expose this, frontend API wrappers consume it, and Sidebar/GraphView UI display PR status, badges, and menus.

Changes

PR/MR listing feature

Layer / File(s) Summary
Forge PR/MR data model and provider listing
src-tauri/src/git/forge.rs
Adds PullRequest struct, list_prs entry point, GitHub/GitLab-specific fetch and parsing via gh/glab CLIs, rollup_checks/normalize_review normalization, and unit tests.
Tauri commands: list_prs and open_url
src-tauri/src/lib.rs
Adds list_prs and open_url async commands (with URL scheme validation) and registers them in the invoke handler.
Frontend API wrappers and PullRequest type
src/types.ts, src/api.ts
Adds PullRequest interface and listPrs/openUrl API wrappers invoking the new backend commands.
RepoView PR state, fetching, and menu wiring
src/components/RepoView.tsx
Adds PR state, refreshPrs, derived branch lookups, openPr/showPrMenu helpers, branch-menu PR entry, and wiring into Sidebar/GraphView.
Sidebar Pull Requests section
src/components/Sidebar.tsx, src/icons.tsx, src/storage.ts, src/styles.css
Adds Sidebar PR section with refresh action, PR rows with avatar/status indicators, new PullRequestIcon/CheckIcon, pullRequests sidebar group default, and supporting CSS.
Graph view PR branch badges
src/components/GraphView.tsx
Threads prBranches through GraphView/CommitRefs/RefBadge/RefIcon to render a fork icon for branches with open PR/MRs.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Sidebar
  participant RepoView
  participant TauriBackend as Tauri Backend
  participant ForgeCLI as gh/glab CLI

  User->>Sidebar: Click "Refresh pull requests"
  Sidebar->>RepoView: onRefreshPrs()
  RepoView->>TauriBackend: listPrs(repo)
  TauriBackend->>ForgeCLI: run gh pr list / glab mr list
  ForgeCLI-->>TauriBackend: JSON PR/MR data
  TauriBackend-->>RepoView: PullRequest[]
  RepoView->>Sidebar: prs, prByBranch, prBranchSet
  Sidebar-->>User: Render PR rows with status
  User->>Sidebar: Click PR row
  Sidebar->>RepoView: onOpenPr(url)
  RepoView->>TauriBackend: openUrl(url)
  TauriBackend-->>User: Open URL in browser
Loading

Poem

A rabbit hops through forks and checks,
Green dots for pass, red for wrecks 🐇
PRs now bloom in the sidebar's light,
Fork icons dance on branches bright,
Thump thump — review's done just right! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main user-facing changes: a Pull Requests sidebar section and the branch fork icon update.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/pull-requests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

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

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

Inline comments:
In `@src-tauri/src/git/forge.rs`:
- Around line 143-171: The PR/MR listing helpers are silently truncating results
because `list_github` hard-caps `gh pr list` at 50 and the GitLab counterpart
uses default CLI paging, so the sidebar can miss items. Update `list_github` and
the GitLab list function to fetch all pages/results (use explicit pagination or
loop until exhausted) instead of relying on fixed defaults, and keep the
existing `PullRequest` mapping intact while aggregating every page into the
returned vector.
- Around line 143-150: The PR refresh path can hang because list_github and
list_gitlab rely on run_cli, which currently uses Command::output() without any
timeout. Update run_cli to enforce a bounded execution time and make sure both
list_github and list_gitlab surface a timeout error instead of waiting
indefinitely. Use the existing run_cli symbol as the central fix point so repo
load and provider changes cannot stall the sidebar refresh.

In `@src/components/RepoView.tsx`:
- Around line 242-252: `refreshPrs` currently swallows failures and can apply
stale responses because it lacks the same request-lifecycle protection used
elsewhere in `RepoView.tsx`. Update `refreshPrs` to use a request guard or
`alive`/request-id check like `refreshStats` and the avatar effects so older
in-flight `api.listPrs(path)` results cannot overwrite newer ones, and stop
using `.catch(() => {})`; instead surface errors through the existing
notification flow, similar to `refreshWipsManually`. Keep the manual
`onRefreshPrs={refreshPrs}` wiring, but route it through a notifying wrapper if
needed so both the mount/provider-change path and the sidebar refresh action
report success/failure consistently.

In `@src/components/Sidebar.tsx`:
- Around line 235-273: The PR row in Sidebar.tsx uses a clickable div with
onClick and onContextMenu but lacks keyboard accessibility and an explicit role.
Update the PR row markup (the branch-row pr element in the PR list render) to
match the accessible pattern used by the branch/tag rows elsewhere in
Sidebar.tsx by adding an appropriate role, keyboard handlers for Enter/Space
activation, and any needed tab focus support so the row is operable by keyboard
and announced correctly by assistive tech.
- Around line 159-174: The refresh button in Sidebar should explicitly set
type="button" so it does not default to submit if rendered inside a form. Update
the button in prActions to include the type attribute alongside the existing
onClick handler and RefreshIcon, keeping the current stopPropagation and
onRefreshPrs behavior unchanged.
- Line 232: The Pull Requests section hides the manual refresh action when prs
is empty, which blocks discovering newly opened PRs from an empty state. In
Sidebar.tsx, update the Group usage for the Pull Requests section so actions
does not depend on prs.length and always exposes prActions; keep the conditional
only if some specific action truly must be hidden, not the refresh control.
Reference the Sidebar component and the prs/prActions wiring to make the refresh
button available even when there are zero pull requests.

In `@src/styles.css`:
- Around line 732-734: The `.pr-ci-pending` style is using a hardcoded hex color
instead of a theme-aware CSS variable, unlike `.pr-ci-success` and
`.pr-ci-failure`. Update the `.pr-ci-pending` rule in `src/styles.css` to use an
existing CSS custom property if available, or add a new variable alongside the
other CI status colors and reference it here so the pending state matches
light/dark theme variants consistently.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c1d7bc67-09ad-470d-8750-e046ecef83c5

📥 Commits

Reviewing files that changed from the base of the PR and between 1e6b46e and da5ea43.

📒 Files selected for processing (10)
  • src-tauri/src/git/forge.rs
  • src-tauri/src/lib.rs
  • src/api.ts
  • src/components/GraphView.tsx
  • src/components/RepoView.tsx
  • src/components/Sidebar.tsx
  • src/icons.tsx
  • src/storage.ts
  • src/styles.css
  • src/types.ts

Comment on lines +143 to +150
fn list_github(dir: &Path) -> AppResult<Vec<PullRequest>> {
let out = run_cli(
dir,
&[
"gh", "pr", "list", "--state", "open", "--limit", "50", "--json",
"number,title,url,headRefName,isDraft,author,statusCheckRollup,reviewDecision",
],
)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map relevant files and inspect the CLI wrapper plus call sites.
git ls-files | rg '(^|/)src-tauri/src/git/|(^|/)src-tauri/src/.*run_cli|(^|/)src-tauri/src/.*cli|(^|/)src-tauri/src/.*provider'
rg -n "fn run_cli|run_cli\(|list_github\(|list_gitlab\(|refreshPrs\(" src-tauri/src -S

# Read the likely implementation slices.
for f in \
  src-tauri/src/git/forge.rs \
  src-tauri/src/git/mod.rs \
  src-tauri/src/git/*.rs
do
  [ -f "$f" ] || continue
  echo "===== $f ====="
  wc -l "$f"
done

sed -n '1,240p' src-tauri/src/git/forge.rs

Repository: jcardonne/gitchef

Length of output: 11061


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== forge.rs around list_gitlab ==="
sed -n '240,316p' src-tauri/src/git/forge.rs

echo "=== list_prs callers / refreshPrs ==="
rg -n "list_prs\(|refreshPrs|provider change|repo load|load repo|open.*repo|auto.*prs|prs" src-tauri/src -S

echo "=== other run_cli wrappers ==="
rg -n "Command::new\(.*output\(|Command::new|timeout|kill\(" src-tauri/src -S

Repository: jcardonne/gitchef

Length of output: 12686


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== frontend PR refresh flow ==="
rg -n "refreshPrs|list_prs|provider change|repo load|loadRepo|openRepo|selectedRepo|remote provider|GitHub|GitLab" . -S

echo "=== files that mention PR sidebar state ==="
rg -n "PR sidebar|pull request|merge request|prs" src* . -g '!target' -g '!node_modules' -S

echo "=== search for timeout/cancellation around invoke calls ==="
rg -n "AbortController|timeout|setTimeout|clearTimeout|cancel.*invoke|invoke\(" . -S

Repository: jcardonne/gitchef

Length of output: 16938


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== RepoView PR refresh handler ==="
sed -n '230,270p' src/components/RepoView.tsx

echo "=== Tauri list_prs command ==="
sed -n '140,160p' src-tauri/src/lib.rs

echo "=== forge.rs run_cli and list_prs ==="
sed -n '1,170p' src-tauri/src/git/forge.rs

Repository: jcardonne/gitchef

Length of output: 9019


Add a timeout around the PR CLI calls run_cli() still uses Command::output() with no timeout, and both list_github() and list_gitlab() are invoked automatically on repo load/provider change. If gh/glab hangs on auth or a dead network, the PR refresh never settles and the sidebar stays stale.

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

In `@src-tauri/src/git/forge.rs` around lines 143 - 150, The PR refresh path can
hang because list_github and list_gitlab rely on run_cli, which currently uses
Command::output() without any timeout. Update run_cli to enforce a bounded
execution time and make sure both list_github and list_gitlab surface a timeout
error instead of waiting indefinitely. Use the existing run_cli symbol as the
central fix point so repo load and provider changes cannot stall the sidebar
refresh.

Comment on lines +143 to +171
fn list_github(dir: &Path) -> AppResult<Vec<PullRequest>> {
let out = run_cli(
dir,
&[
"gh", "pr", "list", "--state", "open", "--limit", "50", "--json",
"number,title,url,headRefName,isDraft,author,statusCheckRollup,reviewDecision",
],
)?;
let prs: Vec<GhPr> = serde_json::from_str(&out)
.map_err(|e| AppError::Msg(format!("could not parse `gh pr list` output: {e}")))?;
Ok(prs
.into_iter()
.map(|p| {
let author_avatar = (!p.login().is_empty())
.then(|| format!("https://github.com/{}.png?size=40", p.author.login));
PullRequest {
number: p.number,
title: p.title,
url: p.url,
branch: p.head_ref_name,
draft: p.is_draft,
author: p.author.login.clone(),
author_avatar,
checks: rollup_checks(&p.status_check_rollup),
review: normalize_review(p.review_decision.as_deref()),
}
})
.collect())
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Silent truncation of PR/MR lists.

gh pr list is capped at --limit 50, and glab mr list uses no --per-page/--page flag, so it defaults to GitLab CLI's built-in page size of 30. Repos with more open PRs/MRs than these caps will silently show an incomplete sidebar list with no indication more exist.

♻️ Proposed fix
     let out = run_cli(dir, &["glab", "mr", "list", "--output", "json"])?;
+    // Without --per-page, glab defaults to 30 results per page.
-    let out = run_cli(dir, &["glab", "mr", "list", "--output", "json"])?;
+    let out = run_cli(dir, &["glab", "mr", "list", "--output", "json", "--per-page", "50"])?;

Also applies to: 240-258

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

In `@src-tauri/src/git/forge.rs` around lines 143 - 171, The PR/MR listing helpers
are silently truncating results because `list_github` hard-caps `gh pr list` at
50 and the GitLab counterpart uses default CLI paging, so the sidebar can miss
items. Update `list_github` and the GitLab list function to fetch all
pages/results (use explicit pagination or loop until exhausted) instead of
relying on fixed defaults, and keep the existing `PullRequest` mapping intact
while aggregating every page into the returned vector.

Comment on lines +242 to +252
// Open PRs/MRs for the sidebar section + the badge fork-icon. A network CLI hit,
// so it's OFF the hot refresh() path: fetched on load / provider change and via
// the section's manual refresh, never after every git op. Non-forge repo -> [].
const refreshPrs = useCallback(() => {
if (repo?.provider !== "github" && repo?.provider !== "gitlab") {
setPrs([]);
return;
}
api.listPrs(path).then(setPrs).catch(() => {});
}, [path, repo?.provider]);
useEffect(() => refreshPrs(), [refreshPrs]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

refreshPrs swallows errors and lacks a stale-response guard.

refreshPrs doubles as the mount/provider-change effect handler and the manual "refresh" action wired at Line 1680 (onRefreshPrs={refreshPrs}), but:

  • It has no id/alive guard like the file's other async fetches (refreshStats's statsReq, commitReq, fileReq, or the avatar effects' alive flag), so an in-flight request from mount/provider-change can resolve after a newer manual-refresh request and silently overwrite it with stale data.
  • Failures are swallowed via .catch(() => {}). The sibling manual-refresh action, refreshWipsManually, notifies on both success and failure — clicking "refresh PRs" when gh/glab fails (not authenticated, CLI missing, rate-limited) gives the user no feedback at all.
🛡️ Proposed fix: add a request guard and a notifying manual-refresh wrapper
+  const prsReq = useRef(0);
   const refreshPrs = useCallback(() => {
     if (repo?.provider !== "github" && repo?.provider !== "gitlab") {
       setPrs([]);
-      return;
+      return Promise.resolve();
     }
-    api.listPrs(path).then(setPrs).catch(() => {});
+    const id = ++prsReq.current;
+    return api.listPrs(path).then((list) => {
+      if (prsReq.current === id) setPrs(list);
+    });
   }, [path, repo?.provider]);
-  useEffect(() => refreshPrs(), [refreshPrs]);
+  useEffect(() => {
+    refreshPrs().catch(() => {});
+  }, [refreshPrs]);
+  const refreshPrsManually = () =>
+    refreshPrs()
+      .then(() => notify("Refreshed pull requests"))
+      .catch((e) => notify(String(e), true));

And at Line 1680:

-          onRefreshPrs={refreshPrs}
+          onRefreshPrs={refreshPrsManually}

Also applies to: 1674-1680

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

In `@src/components/RepoView.tsx` around lines 242 - 252, `refreshPrs` currently
swallows failures and can apply stale responses because it lacks the same
request-lifecycle protection used elsewhere in `RepoView.tsx`. Update
`refreshPrs` to use a request guard or `alive`/request-id check like
`refreshStats` and the avatar effects so older in-flight `api.listPrs(path)`
results cannot overwrite newer ones, and stop using `.catch(() => {})`; instead
surface errors through the existing notification flow, similar to
`refreshWipsManually`. Keep the manual `onRefreshPrs={refreshPrs}` wiring, but
route it through a notifying wrapper if needed so both the mount/provider-change
path and the sidebar refresh action report success/failure consistently.

Comment on lines +159 to +174
// Hover-revealed "refresh pull requests" button (a network CLI call, on demand).
const prActions = (
<span className="group-actions">
<button
className="group-action"
title="Refresh pull requests"
onClick={(e) => {
e.stopPropagation();
onRefreshPrs();
}}
>
<RefreshIcon />
</button>
</span>
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add explicit type="button" to the refresh button.

Static analysis flags this: a <button> with no type defaults to submit, which can trigger accidental form submission if this component is ever nested in a <form>.

🔧 Proposed fix
       <button
         className="group-action"
         title="Refresh pull requests"
+        type="button"
         onClick={(e) => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Hover-revealed "refresh pull requests" button (a network CLI call, on demand).
const prActions = (
<span className="group-actions">
<button
className="group-action"
title="Refresh pull requests"
onClick={(e) => {
e.stopPropagation();
onRefreshPrs();
}}
>
<RefreshIcon />
</button>
</span>
);
// Hover-revealed "refresh pull requests" button (a network CLI call, on demand).
const prActions = (
<span className="group-actions">
<button
className="group-action"
title="Refresh pull requests"
type="button"
onClick={(e) => {
e.stopPropagation();
onRefreshPrs();
}}
>
<RefreshIcon />
</button>
</span>
);
🧰 Tools
🪛 React Doctor (0.5.8)

[warning] 162-162: Your users can submit the form by accident because a <button> with no type defaults to submit.

Set an explicit button type so plain buttons do not submit forms by accident: type="button", "submit", or "reset".

(button-has-type)

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

In `@src/components/Sidebar.tsx` around lines 159 - 174, The refresh button in
Sidebar should explicitly set type="button" so it does not default to submit if
rendered inside a form. Update the button in prActions to include the type
attribute alongside the existing onClick handler and RefreshIcon, keeping the
current stopPropagation and onRefreshPrs behavior unchanged.

Source: Linters/SAST tools

))}
</Group>

<Group title="Pull Requests" icon={<PullRequestIcon />} count={prs.length} open={open.pullRequests} onToggle={() => toggle("pullRequests")} actions={prs.length ? prActions : undefined}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Refresh action is hidden exactly when it's most needed.

actions={prs.length ? prActions : undefined} disables the manual refresh button when prs is empty. Since PR fetching only happens on load, provider change, or this manual refresh (per PR objectives, not the regular refresh() path), this button is the only way to discover a newly-opened PR when the section currently shows zero PRs — but it's unavailable in exactly that state.

🐛 Proposed fix
-      <Group title="Pull Requests" icon={<PullRequestIcon />} count={prs.length} open={open.pullRequests} onToggle={() => toggle("pullRequests")} actions={prs.length ? prActions : undefined}>
+      <Group title="Pull Requests" icon={<PullRequestIcon />} count={prs.length} open={open.pullRequests} onToggle={() => toggle("pullRequests")} actions={prActions}>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Group title="Pull Requests" icon={<PullRequestIcon />} count={prs.length} open={open.pullRequests} onToggle={() => toggle("pullRequests")} actions={prs.length ? prActions : undefined}>
<Group title="Pull Requests" icon={<PullRequestIcon />} count={prs.length} open={open.pullRequests} onToggle={() => toggle("pullRequests")} actions={prActions}>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Sidebar.tsx` at line 232, The Pull Requests section hides the
manual refresh action when prs is empty, which blocks discovering newly opened
PRs from an empty state. In Sidebar.tsx, update the Group usage for the Pull
Requests section so actions does not depend on prs.length and always exposes
prActions; keep the conditional only if some specific action truly must be
hidden, not the refresh control. Reference the Sidebar component and the
prs/prActions wiring to make the refresh button available even when there are
zero pull requests.

Comment on lines +235 to +273
<div
key={pr.number}
className="branch-row pr"
title={`#${pr.number} ${pr.title}\n${pr.branch} · @${pr.author}`}
onClick={() => onOpenPr(pr.url)}
onContextMenu={(e) => {
e.preventDefault();
onPrMenu(pr);
}}
>
{pr.author_avatar ? (
<img className="pr-avatar" src={pr.author_avatar} alt="" />
) : (
<span className="pr-avatar pr-avatar-fallback" aria-hidden="true">
{pr.author.charAt(0).toUpperCase() || "?"}
</span>
)}
<span className="pr-num">#{pr.number}</span>
{pr.checks !== "none" && (
<span className={`pr-ci pr-ci-${pr.checks}`} title={`CI: ${pr.checks}`} />
)}
{pr.review === "approved" && (
<span className="pr-review approved" title="Approved">
<CheckIcon size={11} />
</span>
)}
{pr.review === "changes_requested" && (
<span className="pr-review changes" title="Changes requested">
<CloseIcon size={11} />
</span>
)}
<span className="branch-name pr-title">{pr.title}</span>
{pr.draft && (
<span className="sm-badge" title="Draft">
draft
</span>
)}
</div>
))}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

PR row click handler lacks keyboard support/role.

Static analysis flags the clickable PR row div: no onKeyDown/onKeyUp pairing with onClick, and no role for screen readers. This mirrors the existing branch/tag row pattern elsewhere in the file, so it's not a regression specific to this change, but worth fixing alongside if accessibility is a goal.

🧰 Tools
🪛 React Doctor (0.5.8)

[warning] 235-235: Keyboard users can't trigger this click handler because there's no keyboard one, so add onKeyUp, onKeyDown, or onKeyPress.

Pair onClick with a key handler so keyboard users can trigger it.

(click-events-have-key-events)


[warning] 235-235: Screen reader users can't tell this click handler is interactive because it has no role, so add a role or use a button or link.

Give clickable static elements a role, or use a button or link.

(no-static-element-interactions)

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

In `@src/components/Sidebar.tsx` around lines 235 - 273, The PR row in Sidebar.tsx
uses a clickable div with onClick and onContextMenu but lacks keyboard
accessibility and an explicit role. Update the PR row markup (the branch-row pr
element in the PR list render) to match the accessible pattern used by the
branch/tag rows elsewhere in Sidebar.tsx by adding an appropriate role, keyboard
handlers for Enter/Space activation, and any needed tab focus support so the row
is operable by keyboard and announced correctly by assistive tech.

Source: Linters/SAST tools

Comment thread src/styles.css
Comment on lines +732 to +734
.pr-ci-pending {
background: #e0af68;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hardcoded color breaks theme consistency.

.pr-ci-pending uses a literal #e0af68 while .pr-ci-success/.pr-ci-failure use var(--add)/var(--del). This won't adapt to light/dark theme variants the way the other two do.

♻️ Suggested fix (add a CSS variable if one doesn't already exist)
 .pr-ci-pending {
-  background: `#e0af68`;
+  background: var(--pending, `#e0af68`);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.pr-ci-pending {
background: #e0af68;
}
.pr-ci-pending {
background: var(--pending, `#e0af68`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/styles.css` around lines 732 - 734, The `.pr-ci-pending` style is using a
hardcoded hex color instead of a theme-aware CSS variable, unlike
`.pr-ci-success` and `.pr-ci-failure`. Update the `.pr-ci-pending` rule in
`src/styles.css` to use an existing CSS custom property if available, or add a
new variable alongside the other CI status colors and reference it here so the
pending state matches light/dark theme variants consistently.

The hasPr icon condition also required a local branch ref, so a PR branch
that isn't checked out locally (shown as origin/x only) rendered the cloud
instead of the fork. Drop that requirement - any PR-matched branch shows the
single fork.
@jcardonne
jcardonne merged commit 7244f0f into main Jul 5, 2026
3 checks passed
@jcardonne
jcardonne deleted the feat/pull-requests branch July 5, 2026 13:01
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 0.27.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant