Skip to content

Inside the Popup

Most of these docs explain what happens in the background. This page is the other side of the glass: what the popup actually shows you, and why it shows it that way. It is the closest thing to a user manual, written against the same code as everything else, so the tab counts, sort rules, empty-state copy, and settings defaults here are the real ones.

The popup is a single React app that boots fresh every time you open it and paints from chrome.storage.local before its first frame. How the data gets there is covered in Data Hydration and Storage; this page assumes the data has already arrived and looks at how it is laid out.


From top to bottom, the popup is always the same five regions:

RegionWhat it holds
HeaderThe “last updated” label and the manual refresh button.
BannersA dismissable global-error strip, then the parser-breakage and outage banners when they apply.
TabsTo Review, Authored, Merged, each with a live count.
ListThe pull requests for the active tab.
Settings gearA fixed control in the bottom-right corner that opens the settings overlay.

Everything below unpacks the tabs, the list states, the toolbar badge, and the settings panel.


There are exactly three tabs. The default on open is To Review.

TabLabel shownCount meansOrder
assignedTo ReviewPRs awaiting your review (pending only)Pending first, then already reviewed
authoredAuthoredAll of your open authored PRsGrouped by review state (see below)
mergedMergedAll recently merged PRsAs returned, newest GitHub order

A subtle point worth stating plainly: the To Review count is not the total number of rows on the tab. It counts only the PRs still pending your review, so a tab that shows three reviewed PRs and zero pending reads as 0. The toolbar badge uses the same pending count, which is covered further down.

The assigned list splits into two blocks: PRs still pending your review render first, then PRs you have already reviewed render below them, visually de-emphasised. The reviewed block is there so a PR does not vanish the instant you review it (which would feel like it was lost), but it stays out of the way of the work that still needs you.

The authored tab groups your PRs by their current review state and renders the groups in a fixed priority order, so the ones that need your attention sit at the top:

  1. Changes requested
  2. Approved
  3. Pending
  4. Commented
  5. Draft

Only non-empty groups appear, so the tab never shows an empty heading. Each row carries a small status badge that names its state:

StateBadge labelColour cue
changes_requestedChangesError (red)
approvedApprovedSuccess (green)
pendingPendingWarning (amber)
commentedCommentedInfo (blue)
draftDraftNeutral (grey)

These badges are unique to the Authored tab, because it is the only tab where the review state is about your own PR rather than a request aimed at you.

The merged tab is the simplest: it renders the stored list in the order GitHub returned it, with no client-side regrouping. It is a record of recently shipped work, so order and grouping matter less than just having it on hand.


PRs that arrived since you last looked are marked new and animate in when the tab is shown. The “new” marker is cleared the moment you leave the tab: switching away marks every currently visible new PR as seen, so it does not keep pulsing the next time you open the popup. Reviewed PRs never animate, even if they are technically new, because the entrance flourish is meant to draw your eye to work that still needs doing.

This is purely a popup-side affordance. Whether a PR fires a desktop notification and a sound is a separate decision made in the background, described in Notifications and Sound.


The list area has two distinct “nothing to show” states, and they say different things on purpose.

Before the very first successful load (a freshly installed extension that has not fetched yet), every tab shows the same call to action:

Click the refresh button to load your PRs

Once a load has happened and the list is genuinely empty, each tab shows its own quiet message instead:

TabMessageSubtext
To Review”No PRs to review""PRs requesting your review will appear here”
Authored”No PRs authored by you""PRs you authored will appear here”
Merged”No merged PRs""Merged PRs will appear here”

The split matters: “click refresh” means Pullwatch has not looked yet, while “No PRs to review” means it looked and there genuinely is nothing. Confusing the two would make an empty inbox look broken.


The number on the Pullwatch toolbar icon is the pending To Review count, the same figure as the To Review tab. It respects the Show drafts in list setting, so a draft you have chosen to hide does not inflate the badge.

BadgeMeaning
(blank)Nothing pending
1 to 99That many pending reviews
99+More than ninety-nine pending
...A fetch is in progress
!Parser breakage or a GitHub outage flag is set

The badge is derived from storage every time the worker wakes, without a GitHub round trip, so it is correct even on a cold start before any fetch runs. The error state takes precedence: when a parser-breakage or outage flag is set, the badge shows ! rather than a count, because a stale count during an outage would be misleading.


The gear in the bottom-right corner opens the settings overlay. Changes auto-save as you make them (debounced), so there is no save button to remember. The panel is organised into a few sections.

SettingDefaultWhat it does
Enable notificationsOnDesktop alerts when a new PR needs your review.
Notify on draftsOffAlso alert for draft PRs assigned to you.
SoundpingThe sound played with an assigned alert (with an on/off toggle).
Show drafts in listOnWhether draft PRs appear in the To Review list (and count toward the badge).

There is a guardrail here: turning Notify on drafts on while Show drafts in list is off is treated as off, because alerting on a PR you cannot see in the list would be a dead-end. The full reasoning is in Notifications and Sound.

SettingDefaultWhat it does
Enable notificationsOffDesktop alerts when one of your PRs is merged (opt-in).
SoundbellThe sound played with a merged alert.

Each notification section has a Preview button that fires a sample notification so you can hear the sound and see the toast. It is throttled to roughly one preview every five seconds, and it surfaces a clear message if Chrome itself is blocking notifications.

Beyond the built-in sounds you can upload your own. The custom-sound editor lets you drop in a WAV file, trim it to the slice you want, and save it to a slot, after which it behaves like any other sound choice. If a saved custom sound is ever missing (for example on a second machine that has not downloaded it yet), playback falls back to ping rather than going silent. The storage and playback mechanics live in Notifications and Sound.

SettingDefaultWhat it does
Link opening behaviourForegroundForeground switches to the new tab and closes the popup; Background opens the PR silently and keeps the popup open.
SettingDefaultWhat it does
Popup sizeCompactThree preset shells: Compact (380 × 400), Cozy (418 × 440), Comfortable (456 × 480).
Theme(DaisyUI default)One of 35 built-in DaisyUI themes, with a button to pick one at random.

Pullwatch reads whichever GitHub session your browser holds, so signing into a different GitHub account changes whose PRs it should show. The next fetch notices the viewer identity changed, clears the cached route hint, and rebaselines the lists for the new account rather than diffing them against the previous account’s PRs. That last part is what stops an account switch from firing a wall of “new PR” notifications for PRs that were simply never yours on this account. The storage-integrity side of this (how Pullwatch avoids ever pairing one account’s identity with another’s stored lists) is in Data Hydration and Storage.

When the popup sees the stored identity change to a different login, it reloads its three PR lists from storage (the new account’s lists, which the fetch wave has already written) instead of clearing them to empty. That keeps the switch from briefly showing blank lists while still never rendering the previous account’s rows. A first sign in (no previous login) is not treated as a switch, so the lists the wave just seeded stay put.


Two things stay out of the way until you find them: a small squash minigame that reveals itself after the popup has been opened enough times, and a developer-only debug panel with a deliberately obscure way in. Neither is part of normal use; the minigame has its own external docs.