Harish Kumar
war-storyarchitect-mindset

AstraBox — Chrome Extension Built for My Team's Daily Dev Workflow

I built a Chrome DevTools extension to eliminate two daily friction points for my team: API debugging across page navigation, and assembling PR comments for AI-assisted code review.

May 1, 20259 min

The browser Network tab is technically correct and practically useless for what we actually do.

Every request made by the Capillary Engage UI goes through it — analytics pings, prefetch calls, health checks, and the five API calls you actually care about, all interleaved in a live stream that resets the moment you navigate. If you want to inspect a specific API response, you're either fast or you're starting over. When you're deep in debugging a data issue across multiple page transitions, "fast" is rarely an option.

That's problem one. Problem two sits in the PR review workflow.

CodeRabbit drops twelve comments on your PR. Your teammate adds three more. You open Cursor. Now you're doing the one thing that should never be manual: copy a comment, paste it, copy another, paste it, explain the context for each one, repeat until you've assembled something that looks like a prompt. This takes ten minutes every single code review. Multiply that across a team doing multiple PRs per week.

Neither of these problems had a ticket. Nobody was going to write a ticket for "Chrome's Network tab is annoying" or "copying PR comments is tedious." But both were costing real time, every day, in small chunks that never made the retrospective.

So I built AstraBox.

Capillary Engage is a multi-channel marketing automation platform — campaigns, templates, audience segmentation, asset delivery across WhatsApp, Email, SMS, and more. The UI is a React app where developers routinely debug API calls across page transitions, and code reviews generate CodeRabbit feedback on top of human reviewer comments. Both friction points were specific to this workflow context, not generic browser problems.


The decision: build it

Both problems shared the same constraint — they lived inside the browser, in the dev's normal workflow. Any solution that added a separate tool, a separate process, or a separate window would just add a third thing to manage. The right shape was a DevTools panel: already open, zero context switch, part of the same session as the debugging work itself.

A Chrome extension, distributed load-unpacked, no Chrome Web Store overhead. Two features, one panel.

The architectural cost of this choice is MV3's security model: service workers get killed after roughly thirty seconds of inactivity, losing all in-memory state on every wakeup; the DevTools clipboard API is sandboxed in a way that requires a four-hop delegation chain to copy text. Both costs are real. Both are addressable. Worth naming before describing the features they constrain.

The constraints

Any solution had to live inside the browser — adding a separate server, proxy, or process alongside the dev workflow defeats the purpose. It couldn't require new tooling budget or infrastructure approval. It had to be invisible to adopt: clone it, load it unpacked, done. Distribution through the Chrome Web Store was out — too slow, too much overhead for an internal tool.

What It Is

AstraBox is a Chrome extension that lives as a DevTools panel. MV3 manifest, service worker background, Webpack 5 build. Distributed load-unpacked — no Chrome Web Store, no .crx. You clone it, load it, and it's there every time you open DevTools.

It ships two features: a Network Interceptor and a GitHub PR Comment Extractor. Both are usable today. Both are already in the team's daily workflow.


Feature 1: The Network Interceptor

The idea is obvious once you feel the pain. Instead of watching everything scroll past in the Network tab, you tell AstraBox which URLs you care about — wildcard patterns like https://api.capillary.*/ — and it captures only those. Every matching request gets stored: method, status, URL, size, full headers, full response body. It survives page navigation. You can inspect it after the fact, decompress GZIP responses on demand, toggle between JSON / XML / HTML / text, and page through up to 200 requests in a bounded FIFO queue.

The popup shows live stats: total requests, success count, error count, refreshed every two seconds. The DevTools panel shows the full table with a detail pane. It's the Network tab, filtered to what actually matters, with memory.

That's the feature. Here's the problem it's built on.

What was ruled out first

The obvious path is the webRequest API — Chrome's documented way to observe network traffic from an extension. The problem: webRequest gives you request and response headers but not the response body. You can see the status code and headers; you can't read the JSON. For API debugging, the body is usually the only part that matters.

Proxy-based interception was the other candidate. Route traffic through a local proxy, capture it there. Works, but it adds infrastructure to a tooling problem — now every team member needs to configure a proxy, manage certs, and deal with a running process alongside their dev server. The extension lives inside the browser; the solution should too.

The two-script relay is what remains when you rule those out. It's not a workaround — it's the only architectural path that gets you both fetch wrapping and message delivery within Chrome's security model.

The MV3 Isolation Trap

To intercept fetch(), you have to wrap it before the page code runs. That means running at document_start in the MAIN world — the same JavaScript context as the page itself. Wrap window.fetch, capture what you need, call through to the original.

The problem: from MAIN world, you cannot access chrome.runtime. You can't send a message to the background service worker. You can intercept the request, but you can't get the data out.

From ISOLATED world — where content scripts normally run — you can access chrome.runtime just fine. But from ISOLATED world, you cannot touch window.fetch. Chrome's security model is deliberate here. The two worlds don't share a JavaScript environment. ISOLATED world can't affect the page's fetch.

Most extensions that try to intercept network traffic end up using the webRequest API instead, which gives you headers but not response bodies. If you need the body, you either use a proxy or you find another way.

The two-script relay is the other way.

content_main.js runs in MAIN world. It wraps window.fetch, clones the response ReadableStream (you can only read it once — miss this and you've swallowed the data the page needed), captures method, URL, headers, status, and body, then fires a window.postMessage().

content_isolated.js runs in ISOLATED world. It listens for those messages on window, validates they came from AstraBox (not from page code), and forwards via chrome.runtime.sendMessage() to the background worker.

Two scripts. Two worlds. One message hop across the boundary that Chrome says you can't cross directly.

Both scripts are registered dynamically via chrome.scripting.registerContentScripts() when the user saves URL patterns. They can't be declared statically in the manifest because the match patterns are user-defined at runtime — you don't know ahead of time which domains someone wants to intercept.


Feature 2: The PR Comment Extractor

This one starts from a different kind of frustration — the kind where the problem is obvious and the solution is just never prioritized because it feels too small.

Engineers on the team use Cursor for AI-assisted code review resolution. The workflow is: read a PR comment, open Cursor, explain the comment, paste the relevant code, ask for a fix. Then repeat for the next comment. And the next.

AstraBox collapses that into one click.

On any GitHub PR page, a floating button appears. Click it. AstraBox extracts the PR title, description, and every review comment — CodeRabbit bot and human reviewers — then formats them into a structured markdown prompt. That prompt goes to the clipboard. You paste it into Cursor once. Cursor has the full picture.

The DevTools panel has a GitHub tab with the same capability plus filtering. You can separate CodeRabbit comments from human reviewer comments, browse them in a table, and hit "Prepare for Cursor" when you're ready.

The workflow goes from ten manual copy-pastes to one click. That's the entire point.

What was ruled out first

The obvious path was to use the GitHub API to fetch PR data server-side. The problem: the GitHub API requires authentication, and storing a PAT in an extension that runs in every browser tab is a security surface I didn't want to own. A bookmark snippet was another option — a one-liner that grabs visible comments from the DOM. It works for simple cases but doesn't handle CodeRabbit's paginated comment structure or the DevTools panel context. DOM scraping from a content script was the cleanest path: runs in the page context, sees the fully-rendered HTML, no auth required, and the data is already there.

The Clipboard Chain

The floating button (FAB) runs on the GitHub page. It has a user gesture context — the click event. navigator.clipboard.writeText() works fine. That's the easy path.

The DevTools panel is different. A DevTools panel runs in an iframe that Chrome sandboxes away from the page. It has no user gesture context. navigator.clipboard.writeText() is blocked. Full stop.

The question then becomes: what does have clipboard access from a DevTools panel context? The background service worker doesn't — background context has no user gesture either. Sending a message from the panel to the background to do the write doesn't help; the background is equally sandboxed from clipboard operations.

I tried the offscreen document approach — Chrome MV3 added this as a way to do DOM operations from the background. It didn't work for clipboard access in this situation. The tombstone comment is still in background.js:20: "This has been removed as we are now using the foreground for clipboard operations."

The working solution is a four-hop delegation chain:

  1. Panel sends COPY_TO_CLIPBOARD_REQUEST to the background service worker
  2. Background queries for the active tab
  3. Background sends a message to that tab's content script
  4. Content script creates a hidden textarea, writes the text, calls document.execCommand('copy'), removes the textarea

Four hops to do what a single navigator.clipboard.writeText() call does on a page with a user gesture. This is the tax you pay for running inside a sandboxed DevTools panel under MV3's security model.

The FAB and the panel coexist. Same feature, two code paths, because Chrome's context rules are different in each location.


The Third Problem: State That Doesn't Survive

MV3 replaced persistent background pages with service workers. The tradeoff Chrome made: lower memory footprint, better battery life, but service workers get killed after roughly thirty seconds of inactivity. When they wake back up, they start fresh. All in-memory state is gone.

For a simple extension this barely matters. For AstraBox, where the Network Interceptor maintains request history and the ConfigManager holds user-defined URL patterns and plugin state, this is a real constraint.

The solution is treating chrome.storage.local as the only source of truth. In-memory state is always a cache, never authoritative.

On every wakeup, background.js reconstructs fully from storage: plugins reload their configs, the request store rehydrates, content script registrations get re-evaluated against saved URL patterns. The ConfigManager wraps all storage access with an in-memory cache and a FIFO promise queue that serializes concurrent writes — concurrent writes to chrome.storage.local can race in ways that silently corrupt state.

The result: service worker can die and wake as many times as Chrome wants. The user experience is continuous.


The plugin architecture decision

The PackageUpgradePlugin is already implemented in the background layer — it authenticates with a GitHub PAT, fetches package.json, bumps dependency versions, and opens a PR. No DevTools tab surfaces it yet.

I built this before building a UI for it. The reason: building the plugin system first meant every future feature gets the same routing, storage, and lifecycle guarantees for free. Building the UI first and backfilling a plugin system would have forced a rewrite. The locked tabs visible in the extension UI — Release, Debug, Automation, AI Assist — are placeholders for plugins that follow the same pattern. The architecture shapes what's easy to add next, and this is the easiest shape to extend.


Outcome

Three developers run the Network Interceptor daily during active development. The interceptor stays on; debugging sessions happen normally without managing the Network tab. API calls accumulate in the panel — inspectable, filterable, GZIP-decompressed on demand — whether or not there was a page navigation in between.

On code reviews, the Comment Extractor replaced the manual assembly step entirely. What used to be ten minutes of copy-paste per review is now one clipboard paste before opening Cursor. The team does multiple PRs per week; that time adds up.

What Changed

For a tool that sits inside the browser devs already have open, what matters isn't the feature list — it's the absence of the friction that was there before. The debugging loop no longer depends on catching the right moment. The code review prep no longer starts with an assembly task. Both things that were costing time every day, invisibly, stopped costing it.