Skip to content

Commit 11a33f4

Browse files
authored
feat(dashboard): add clickable draft-to-ready badge, gate approve on failing CI, and refine dep status (#168)
## Summary - Add a two-step clickable `Draft` / `D` -> `Ready?` pill (`DraftBadge`) for self-authored open draft PRs across Pull Requests, Dependencies, and Tracked Items tabs, backed by `markPullRequestReadyForReview` and a 60s optimistic guard - Gate `canApprovePR` to disallow inline PR approval when CI checks are failing (`checkStatus === "failure"`) - Refine `classifyDepStatus` so enriched non-draft PRs with `checkStatus === null` or `reviewDecision === "APPROVED"` remain in Mergeable (while `CHANGES_REQUESTED` routes to Needs Action) and render explicit check-status reason badges and `/checks` links in DependenciesTab
1 parent 5355b5d commit 11a33f4

30 files changed

Lines changed: 1911 additions & 101 deletions

‎DEPLOY.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ The login flow requests `scope=repo read:org`:
132132
| `repo` | Read issues, PRs, check runs, workflow runs (includes private repos) |
133133
| `read:org` | `GET /user/orgs` — list user's organizations for the org selector |
134134

135-
**Note:** The `repo` scope grants write access to repositories. An Octokit request guard blocks all REST write operations (POST/PUT/PATCH/DELETE) and all GraphQL mutations except explicit user-confirmed PR approvals (`addPullRequestReview` with `event: APPROVE`).
135+
**Note:** The `repo` scope grants write access to repositories. An Octokit request guard blocks all REST write operations (POST/PUT/PATCH/DELETE) and all GraphQL mutations except explicit user-confirmed PR approvals (`addPullRequestReview` with `event: APPROVE`) and marking draft PRs ready (`markPullRequestReadyForReview`).
136136

137137
### Local development OAuth App
138138

‎README.md‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Open issues where you're the creator, assignee, or mentioned. A scope filter let
3333

3434
### Pull Requests
3535

36-
Open PRs with CI status dots (green/yellow/red), review decision badges, a read-only `auto` badge when GitHub auto-merge is enabled (with merge method tooltip), size badges (XS–XL by lines changed), draft indicators, and a two-step inline `Approve` button on PR rows requiring approval. A "blocked" filter catches PRs where checks are failing or a review requested changes. The scope filter works here too. Reviewer avatars stack for multiple reviewers.
36+
Open PRs with CI status dots (green/yellow/red), review decision badges, a read-only `auto` badge when GitHub auto-merge is enabled (with merge method tooltip), size badges (XS–XL by lines changed), clickable two-step `Draft` → `Ready?` badges on your own draft PRs (read-only draft indicators on others'), and a two-step inline `Approve` button on PR rows requiring approval (hidden when CI checks are failing). A "blocked" filter catches PRs where checks are failing or a review requested changes. The scope filter works here too. Reviewer avatars stack for multiple reviewers.
3737

3838
### Actions
3939

@@ -81,7 +81,7 @@ Star counts appear in repo group headers, fetched as part of the standard data r
8181

8282
### Dependencies Tab
8383

84-
An auto-detected built-in tab that appears when dependency bot PRs are found in your tracked repos. Dependency PRs are identified via a multi-layer bot detection pipeline (PR author login, branch name prefix, configurable label). Items are grouped by status — **Needs Review** (CI passing, not yet approved), **Waiting** (CI pending or draft), and **Stale** (open more than 14 days) — rather than by repo. Abandoned dependency pills link directly to the Renovate Dashboard issue for bulk resolution. Configurable via **Settings > Dependencies**.
84+
An auto-detected built-in tab that appears when dependency bot PRs are found in your tracked repos. Dependency PRs are identified via a multi-layer bot detection pipeline (PR author login, branch name prefix, configurable label). Items are grouped by status — **Mergeable** (CI passing or no CI configured, not a draft, and no changes requested), **Pending Rebase**, **Needs Action** (with explicit reason badges for failing/running checks, merge conflicts, drafts, or requested changes), and **Stale** (not updated in over 14 days) — rather than by repo. Abandoned dependency pills link directly to the Renovate Dashboard issue for bulk resolution. Configurable via **Settings > Dependencies**.
8585

8686
### Custom Tabs
8787

@@ -128,8 +128,8 @@ src/
128128
layout/ # Header, TabBar, FilterBar
129129
onboarding/ # OnboardingWizard, OrgSelector, RepoSelector
130130
settings/ # SettingsPage, TrackedUsersSection, ThemePicker, Section, SettingRow
131-
shared/ # 28 shared components: FilterInput, FilterChips, StatusDot,
132-
# ReviewBadge, AutoMergeBadge, SizeBadge, RoleBadge, SortDropdown, PaginationControls,
131+
shared/ # 29 shared components: FilterInput, FilterChips, StatusDot,
132+
# ReviewBadge, AutoMergeBadge, DraftBadge, SizeBadge, RoleBadge, SortDropdown, PaginationControls,
133133
# LoadingSpinner, SkeletonRows, ToastContainer, NotificationDrawer,
134134
# RepoLockControls, UserAvatarBadge, ExpandCollapseButtons,
135135
# RepoGitHubLink, ChevronIcon, ExternalLinkIcon, Tooltip/InfoTooltip
@@ -170,7 +170,7 @@ pnpm run screenshot # Capture dashboard screenshot
170170

171171
## Security
172172

173-
OAuth tokens are stored in `localStorage` under an app-specific key — this is standard for single-user personal dashboards and matches the threat model here. CSP headers block script injection via Cloudflare (`script-src 'self'` with a SHA-256 exception for the dark-mode initialization script only). An Octokit hook blocks all non-GET REST requests and all GraphQL mutations except the single user-confirmed PR approval mutation (addPullRequestReview with event: APPROVE) — the repo scope is required for private repo access, and all other write operations are rejected client-side. OAuth state is generated with `crypto.getRandomValues` and verified on callback. Token validation runs on every page load via `GET /user`; a 401 clears the stored token immediately.
173+
OAuth tokens are stored in `localStorage` under an app-specific key — this is standard for single-user personal dashboards and matches the threat model here. CSP headers block script injection via Cloudflare (`script-src 'self'` with a SHA-256 exception for the dark-mode initialization script only). An Octokit hook blocks all non-GET REST requests and all GraphQL mutations except explicit user-confirmed PR approval (`addPullRequestReview` with `event: APPROVE`) and marking draft PRs ready (`markPullRequestReadyForReview`) — the repo scope is required for private repo access, and all other write operations are rejected client-side. OAuth state is generated with `crypto.getRandomValues` and verified on callback. Token validation runs on every page load via `GET /user`; a 401 clears the stored token immediately.
174174

175175
## Deployment
176176

‎docs/USER_GUIDE.md‎

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ If you prefer not to use OAuth, you can sign in with a GitHub Personal Access To
8181
Two token formats are accepted:
8282

8383
- **Classic tokens** (starts with `ghp_`) — recommended. Works across all organizations you belong to. Required scopes: `repo`, `read:org` (under admin:org).
84-
- **Fine-grained tokens** (starts with `github_pat_`) — also work, but only access one organization at a time. Required permissions: Actions (read), Contents (read), Issues (read), Pull requests (read and write to approve PRs from the dashboard, or read-only for viewing only).
84+
- **Fine-grained tokens** (starts with `github_pat_`) — also work, but only access one organization at a time. Required permissions: Actions (read), Contents (read), Issues (read), Pull requests (read and write to approve PRs or mark draft PRs ready from the dashboard, or read-only for viewing only).
8585

8686
The token is validated against the GitHub API before being stored. It is saved permanently in your browser's `localStorage` — you will not need to re-enter it on revisit.
8787

@@ -227,9 +227,17 @@ The dot links to the PR's checks page on GitHub.
227227

228228
When GitHub auto-merge is enabled on a pull request, a read-only `auto` badge appears next to the review badge (in both comfortable and compact density across the Pull Requests, Dependencies, and Tracked tabs). Hover or focus the badge to view the configured merge method (`merge`, `squash`, or `rebase`).
229229

230+
#### Draft Badge & Mark Ready Action
231+
232+
Draft pull requests display a `Draft` badge (`D` in compact mode) across the Pull Requests, Dependencies, and Tracked tabs. When you are the author of an open draft PR, the badge is interactive and uses a two-step confirmation to mark the PR ready for review on GitHub:
233+
1. Click **Draft** (or **D**) once to arm confirmation (**Ready?**). The confirmation state resets automatically after 4 seconds, on Escape, or when focus leaves the badge.
234+
2. Click **Ready?** to execute `markPullRequestReadyForReview` on GitHub and transition the PR out of draft status.
235+
236+
Draft PRs authored by other users render as a read-only indicator.
237+
230238
#### Inline Approve Action
231239

232-
Open, non-draft pull requests not authored by you show an **Approve** button next to the pull request title on hover when branch protection requires a review (`Review needed`) or when a review has been requested of you or your team (unless you have already approved the PR without a fresh re-request).
240+
Open, non-draft pull requests not authored by you show an **Approve** button next to the pull request title on hover when branch protection requires a review (`Review needed`) or when a review has been requested of you or your team (unless you have already approved the PR without a fresh re-request). The **Approve** button is hidden when CI checks are failing (`checkStatus === "failure"`).
233241

234242
Approval uses a two-step inline confirmation to prevent accidental clicks:
235243
1. Click **Approve** once to arm confirmation (**Confirm?**, or **Confirm (auto)** when auto-merge is enabled on the PR). The confirmation state resets automatically after 4 seconds, on Escape, or when focus leaves the button.
@@ -318,10 +326,10 @@ Unlike the Pull Requests tab (which groups by repo), the Dependencies tab groups
318326

319327
| Group | Criteria |
320328
|-------|----------|
321-
| **Mergeable** | CI passing (all checks green), not yet approved — awaiting review |
329+
| **Mergeable** | CI passing (or no CI configured), not a draft, and no changes requested — ready to review or merge |
322330
| **Pending Rebase** | PR has the configured rebase label — waiting for the bot to rebase |
323-
| **Needs Action** | CI pending, checks still running, or PR is a draft — not yet actionable |
324-
| **Stale** | PR has been open more than 14 days without merging — may need a rebase or manual review |
331+
| **Needs Action** | Failing or running CI checks, merge conflicts, draft PR, or changes requested — rows display explicit reason badges (`Failing checks`, `Merge conflict`, `Checks running`, `Draft`, `Changes`) |
332+
| **Stale** | PR has not been updated in over 14 days — may need a rebase or manual review |
325333

326334
Within each group, PRs are sorted by repository name, then update category (maintenance, pin, digest, patch, minor, major), then update date.
327335

@@ -838,9 +846,9 @@ For detailed per-source API call counts, see Settings > API Usage.
838846

839847
OAuth tokens (from "Sign in with GitHub") work across all your organizations and support all features. Classic PATs with the correct scopes (`repo`, `read:org`) behave identically to OAuth.
840848

841-
Fine-grained PATs are limited to one organization at a time. Required permissions: Actions (read), Contents (read), Issues (read), Pull requests (read and write to approve PRs from the dashboard, or read-only for viewing only).
849+
Fine-grained PATs are limited to one organization at a time. Required permissions: Actions (read), Contents (read), Issues (read), Pull requests (read and write to approve PRs or mark draft PRs ready from the dashboard, or read-only for viewing only).
842850

843-
**Approving a PR fails with a permission error (403 / FORBIDDEN).**
851+
**Approving a PR or marking a draft PR ready fails with a permission error (403 / FORBIDDEN).**
844852

845853
Your token does not have write permission for pull requests. If you are using a fine-grained Personal Access Token, open GitHub Settings, edit or regenerate the token with **Pull requests** set to **Read and write** (`Pull requests: Read and write`), and replace your token in **Settings > Data > Authentication**.
846854

‎mcp/tests/octokit.test.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,21 @@ describe("createOctokitClient — write guard hook", () => {
9292
})
9393
).rejects.toThrow(/Write operation blocked/);
9494
});
95+
96+
it("blocks MARK_PR_READY_MUTATION (markPullRequestReadyForReview) on POST /graphql", async () => {
97+
const { createOctokitClient } = await import("../src/octokit.js");
98+
const { MARK_PR_READY_MUTATION } = await import("../../src/shared/validation.js");
99+
const client = createOctokitClient("fake-token");
100+
101+
await expect(
102+
client.request("POST /graphql", {
103+
query: MARK_PR_READY_MUTATION,
104+
variables: {
105+
pullRequestId: "PR_kwDOtest42",
106+
},
107+
})
108+
).rejects.toThrow(/Write operation blocked/);
109+
});
95110
});
96111

97112
// ── Singleton tests ───────────────────────────────────────────────────────────

0 commit comments

Comments
 (0)