← Writing

August 30, 2026

Reviving a Chrome extension Manifest V2 killed

githunt replaced your new tab with trending GitHub repos. I liked using it, then it stopped working, and the reason turned out to be simple: Chrome disabled Manifest V2 extensions entirely, and githunt's manifest declared manifest_version: 2. No code rot, no broken dependency, just a platform that moved out from under it in 2018-era code.

What you can expect below: a from-scratch Manifest V3 rebuild, a real bug the rebuild introduced and then fixed with an automated design critique, a concrete number for what "no pagination" actually meant once I tried to fix it, and where the project ended up living once it needed a second home outside the extension itself.

The rebuilt extension's new-tab view: a flat, ruled list of trending repos with a keyboard cursor and command-style filters
The rebuilt extension's new-tab view: a flat, ruled list of trending repos with a keyboard cursor and command-style filters

Why it was actually broken

Manifest V2's browser_action and manifest_version: 2 aren't just old syntax, they're keys Chrome's extension loader now refuses outright. Rebuilding meant a real Manifest V3 manifest: chrome_url_overrides.newtab, host_permissions instead of the old wildcard permission scheme, and no more relying on a persistent background page.

One thing worth keeping from the original and one thing worth dropping. Worth keeping: it never had an official trending API to call, because there isn't one. Worth dropping: githunt approximated "trending" through the GitHub Search API, sorted by star count within a creation-date window. That's a different thing from trending. My rebuild scrapes github.com/trending directly instead, which is what "trending" actually means (recent star velocity, not just total stars on new repos), and it works in Manifest V3 without a CORS proxy because host_permissions grants the extension cross-origin fetch rights the browser would otherwise block.

A critique caught a real bug, not just a taste problem

Once the extension worked again, I ran it through an automated design critique (two independent passes, one reading the code, one scripting a real browser against the built extension). Score: 23 out of 36. The finding that mattered wasn't a taste note, it was a genuine defect: the keyboard-selected row's highlight was supposed to invert the row's colors, foreground for background, terminal-style. Instead the highlight was invisible.

The cause was a CSS quirk I hadn't hit before. I'd written a rule that both set a background from var(--ink) and redefined --ink for that same element's children:

.repo--selected {
  background: var(--ink);
  --ink: var(--paper);
}

CSS custom properties don't have a declaration order the way JavaScript statements do. Both lines apply to the same element, so var(--ink) in the background resolves using the already-redefined value, not the one that existed before this rule ran. The background silently became the same color as everything else. Fix: a second variable, --row-ink, defined once at the page root and never redeclared, so the background has something stable to point at while the children still get the inverted color.

"Load more" turned out to be a wrong question

Once the redesign settled, the next ask was to load more repos. github.com/trending has no pagination. One fetch is the entire result GitHub's own page shows, typically 19 to 25 repositories depending on the day. There's no second page to request.

So "more" had to come from combining separate fetches instead of paginating one. The rebuild now fetches the two time windows you didn't pick (today, this week, this month) for the same language filter, and, when no language filter is set, five popular languages for the same time window, all in parallel, then merges in whatever wasn't already in the primary result. Each repo remembers which fetch it actually came from, so a repo pulled in from "this month" still says "this month" next to its count instead of silently borrowing whatever window you have selected.

Concrete result: the default view (weekly, all languages) went from 21 repos to 119.

It needed a second home, and static hosting has no server

The extension was Crewio-branded at first, blue accent and all, since that's where I built it. Once I wrote it up here, keeping that branding stopped making sense: this is a personal project, shown on a personal site, so it should look like it belongs to the site rather than to my employer. The reskin swapped the accent color and font for this site's own Inter and black-on-white ladder, and dropped the Crewio wordmark from the footer for a plain text credit instead.

The harder part was making the tool itself visible somewhere a Chrome extension can't reach: a browser tab that isn't running the extension. cplog.github.io is static GitHub Pages, no server, and the extension's live scrape only works because Manifest V3's host_permissions gives it a cross-origin fetch right a plain webpage doesn't get. A page on this site trying the same fetch against github.com/trending would just get blocked by CORS.

So the live page doesn't fetch live. A GitHub Actions workflow scrapes github.com/trending server-side every six hours and commits the result as a JSON snapshot, same-origin to the page that reads it, no CORS problem because there's no cross-origin request left to make. The page itself is a plain script tag, no build step, reading that snapshot and doing the same since-period merge the extension does, entirely client-side once the data's already local.

The web version at cplog.github.io/trending, same design system as the rest of the site, reading a pre-scraped snapshot instead of fetching live
The web version at cplog.github.io/trending, same design system as the rest of the site, reading a pre-scraped snapshot instead of fetching live

What I'd change next

Five fixed "popular languages" for the merge step is a guess, not a measurement. It should probably be based on what showed up most often across the last several fetches instead of a hardcoded list. And the web version is deliberately thinner than the extension, no language filter, no grid view, no keyboard nav, so its "trending" is never more than six hours old. Both are honest tradeoffs for a first pass, not oversights, but both are also worth fixing before I'd call this finished.

What I'd keep without changing: scraping the real trending page instead of approximating it through search, and the rule that every merged-in repo has to say where it actually came from. Both were cheap decisions that would have been easy to skip and would have quietly made the tool worse.