The author built a Bilibili creator-assistant extension "out of pure laziness" — dozens of comments
needed replies every day. The first version ran in about two hours, but all the code was crammed
into one 1,211-line content.js where "any change felt like defusing a bomb." A later v3.0
refactor shrank content.js to 175 lines across 7 independent modules.
| Reason | Why it matters |
|---|---|
| The technical barrier is just right | No Swift, no backend. HTML + CSS + JavaScript — exactly what Claude Code does best. Claude has a solid grasp of Manifest V3, so it handles Service Workers and permission declarations for you. |
| There is a real use case | You use a browser every day. A tool you use daily is far more motivating than a to-do list you open twice. |
| The feedback loop is instant | Change code → open the extensions page → click "Reload" → refresh the page → see the result. No compilation, no deployment, no review wait. |
The early mistake: jumping straight to "build an auto-reply plugin." Claude produced a very basic version (detect a new comment, reply "Thanks for your support") — functional, but not what was wanted.
The fix: enter Plan mode first (Shift+Tab) and talk through requirements:
I want to build a Bilibili creator assistant Chrome extension. Core requirements:
1. Automatically scan video comments
2. Match comments against keyword rules and auto-reply
3. Support both global rules and per-video rules
4. Don't re-reply to comments that have already been replied to
5. A simple management panel to view running status
Help me analyze these requirements first and propose a technical approach.
In Plan mode Claude outputs a full technical proposal — file structure, technology choices, analysis of tricky parts. Do not rush to say "great, let's start." Read it carefully and raise questions.
Based on the approach we discussed, create the Chrome extension project. Start with the skeleton:
- manifest.json (MV3)
- background.js (Service Worker)
- content.js (Content Script)
- popup.html + popup.js (management panel)
- lib/ directory (business modules)
Only request permissions that are strictly necessary.
manifest.json (the "soul" of an extension){
"manifest_version": 3,
"name": "Bilibili Creator Assistant",
"version": "3.0.0",
"permissions": ["storage", "alarms", "activeTab"],
"host_permissions": ["*://*.bilibili.com/*"],
"background": {
"service_worker": "background.js"
},
"content_scripts": [{
"matches": ["*://*.bilibili.com/video/*"],
"js": ["content.js"]
}],
"action": {
"default_popup": "popup.html"
}
}
| Decision | Why |
|---|---|
"manifest_version": 3 | MV2 is deprecated by Chrome. Claude uses MV3 automatically — but many online tutorials still use MV2, and referencing them can make Claude mix the two APIs. Add "Must use Manifest V3" to CLAUDE.md. |
| Service Worker, not a Background Page | The biggest MV3 change: the background script is a Service Worker the browser can terminate at any time. You cannot use setInterval for scheduled tasks — use chrome.alarms. Put this in CLAUDE.md or Claude occasionally falls back to the old pattern. |
| Minimal permissions | The Chrome Web Store review is more likely to reject extensions with excessive permissions, and users hesitate at a long permission list. |
The most common mistake in extension development is putting code in the wrong place. MV3 has three runtime environments with completely different responsibilities:
| Environment | File | Can do | Cannot do |
|---|---|---|---|
| Service Worker | background.js | Scheduled tasks, global state, message routing | Access page DOM, read page cookies |
| Content Script | content.js | Manipulate page DOM, read page context | Call chrome.alarms and similar APIs directly |
| Popup / Options | popup.js | User interface, configuration management | Run after the user closes the popup |
background.js (Command Center)
├── Owns the timed scan loop (chrome.alarms, once per minute)
├── Routes all messages (popup/content/widget -> centralized handling)
└── Manages global state (on/off switch, rate limit level, etc.)
↕ chrome.runtime.sendMessage
content.js (Front-line Scout)
├── Extracts current video info (BV ID, title, etc.)
├── Proxies API calls (requires page cookies)
└── Forwards logs and status to the floating widget
↕ Message passing
lib/ (Business Brain)
├── db.js -> Unified data layer, all storage reads/writes go here
├── scanner.js -> Comment scanning engine
├── rules.js -> Rule matching logic
├── api.js -> Bilibili API wrapper
├── ai.js -> AI reply generation
└── rate-limiter.js -> 4-level rate limiting with graceful degradation
The key design principle: content.js only does what it absolutely must —
read page cookies (background.js cannot in MV3) and manipulate page DOM. Everything else lives
in background.js and lib/.
Architecture principles:
1. content.js is a thin shell - only extracts video info and proxies API calls
2. background.js is the command center - owns the scan loop and state management
3. All business logic goes in independent modules under lib/
4. All data storage goes through lib/db.js - no direct chrome.storage calls elsewhere
5. Modules communicate via message passing, not shared state
Write these into the project's CLAUDE.md.
CLAUDE.md and
encode the architecture principles there. Every future conversation will respect them — the "give the
AI a map" idea from Chapter 5, applied.db.js) — the foundation everything depends on. Interfaces like getSystemState()/setSystemState(), getConfig()/setConfig(), getRules()/setRules(), getVideo(bvid)/setVideo(), markReplied(bvid, rpid). Storage consolidated from 20+ scattered keys into 5 structured keys (sys:state, sys:config, sys:rules, v:{bvid}). All writes are atomic: read → merge → write, to prevent concurrent overwrites.rules.js) — the smallest module, 62 lines. Input: a comment and a set of rules. Output: the match result. A pure function, no side effects, very easy to test.api.js) — wraps Bilibili's comment fetching and reply sending. Because the API needs page-cookie authentication, these functions actually run in the content.js context and return results via message passing.scanner.js) — wires the previous three together: fetch comments → filter already-replied → match rules → generate reply → send. Uses dependency injection (see code below).rate-limiter.js) — 4-level graceful degradation (see table below).background.js) — assembles all modules, implements the scan loop. Woken by chrome.alarms once per minute, scanning in priority order (currently open tabs first).content.js exposes a few simple methods to the floating widget; popup.js handles the management panel.scanner.jsasync function scanOneVideo(bvid, rules, config, {
sendReplyFn, // function to send a reply
fetchCommentsFn, // function to fetch comments
logFn // logging function
}) {
// business logic
}
scanner.js runs inside background.js, but API calls must execute in the
content.js context. Instead of calling the API directly, scanOneVideo receives
functions from outside. background.js passes in a function that "relays the call to
content.js via messaging," and scanner.js never needs to know that detail. This keeps
the business logic testable and decoupled from the messaging plumbing.
| Level | Reply interval | Scan interval | Trigger |
|---|---|---|---|
| normal | 5 seconds | 2 seconds | Default |
| slow | 15 seconds | 5 seconds | Rate-limit warning received |
| slower | 30 seconds | 10 seconds | 3 consecutive errors |
| paused | Paused | Paused | Account throttled — resumes after 1 hour |
A successful reply decrements the error counter, automatically recovering to normal — "like a spring: compress it, and it bounces back."
| Area | How to debug it |
|---|---|
| Service Worker | On chrome://extensions, click the "Service Worker" link under your extension to open a dedicated DevTools. console.log from background.js appears here, not in the page console. |
| Content Script | See content.js logs in the webpage's DevTools console. Remember content.js runs in an isolated environment and does not share globals with the page's own JavaScript. |
| Reloading | After changing code: click refresh on chrome://extensions. Popup/options pages: close and reopen. content.js: refresh the target page. Service Worker: click "Update" next to the "Service Worker" link. |
During development you do not publish to the Chrome Web Store. On chrome://extensions,
enable "Developer mode," click "Load unpacked," and select your project directory.
A testing checklist (have Claude generate one):
Version 1 worked, but content.js was one 1,211-line file with API calls, scanning logic, rule
matching, and UI updates all tangled together. MV3 Service Workers can sleep at any time, making
setInterval scheduling unreliable. The refactor was not planned — the author wanted to
add AI-assisted replies and found it impossible to work with in that file.
The current content.js is 1,211 lines with all logic mixed together. I want to refactor it into:
- background.js as the operational hub
- content.js as a thin shell (only video info extraction and API proxying)
- lib/ directory for all business modules
Don't do this all at once - work in steps. First help me analyze the current code and map out where each part belongs.
Claude produced a migration plan splitting the 1,211 lines into 8 functional chunks, annotating where each belonged. Then they executed step by step — test after each migration, confirm nothing regressed, then move on.
| File | Lines | Responsibility |
|---|---|---|
background.js | 468 | Message routing + scan orchestration |
content.js | 175 | Video info extraction + message bridging |
lib/db.js | 410 | Unified data layer |
lib/scanner.js | 322 | Comment scanning engine |
lib/api.js | 137 | Bilibili API wrapper |
lib/ai.js | 127 | AI reply generation |
lib/rate-limiter.js | 100 | Rate limiting with graceful degradation |
lib/rules.js | 62 | Rule matching |
lib/migrate.js | 253 | v2 → v3 data migration |
| Idea | What it exercises |
|---|---|
| Web annotation tool — highlight selected text, save locally | Content script DOM manipulation + storage persistence |
| AI translation assistant — select a paragraph, call an AI to translate | Content script + external API calls |
| Social media timer — track time per site, alert when over | Background alarms + multi-site content scripts |
| GitHub enhancer — show CI status on PR pages, auto-apply labels | GitHub API + content script injection |
Whichever you choose, the core workflow is the same: Plan mode to analyze requirements →
define architecture (who owns what) → write CLAUDE.md to lock in the principles →
implement module by module → debug and test.
┌─────────────────────────────────────────────┐
│ POPUP / OPTIONS (popup.js) │
│ UI + config · dies when popup closes │
└───────────────┬─────────────────────────────┘
│ messages
┌──────────────────────▼──────────────────────┐
│ SERVICE WORKER (background.js) │ <- alarms, global state,
│ command center · can be killed anytime │ message routing
└──────────────────────┬──────────────────────┘
│ messages
┌──────────────────────▼──────────────────────┐
│ CONTENT SCRIPT (content.js) THIN SHELL │ <- DOM + page cookies ONLY
└─────────────────────────────────────────────┘
lib/* = all business logic, called from background.js
| v1 (runnable) | v3 (maintainable) | |
|---|---|---|
| Structure | One content.js, 1,211 lines | content.js 175 lines + 7–8 focused modules |
| Scheduling | setInterval (unreliable under MV3) | chrome.alarms |
| Storage | 20+ scattered keys | 5 structured keys, all via db.js |
| Changing a feature | Hunt through the whole file first | Open one module, understand it in isolation |
| Total lines | 1,211 | 2,000+ (more lines, less cognitive load) |
| MV2 (old) | MV3 (current) |
|---|---|
| Background Page (persistent) | Service Worker (can be terminated anytime) |
setInterval for timers | chrome.alarms |
| Still shown in many online tutorials | Required by Chrome; state "Must use Manifest V3" in CLAUDE.md |
content.js.setInterval in an MV3 Service Worker instead of chrome.alarms.chrome.storage calls scattered everywhere instead of one data layer.CLAUDE.md so every session respects them (incl. "Must use Manifest V3", "use chrome.alarms").db.js; make writes atomic (read → merge → write).setInterval in an MV3 Service Worker?content.js?CLAUDE.md?db.js first?scanner.js and why it is used.background.js logs appear versus content.js logs?manifest.json (MV3) requesting only the permissions your idea truly needs, and justify each one.CLAUDE.md with 5 architecture principles including "Must use Manifest V3."rules.js) and write two tests for it.Build a Chrome Extension
│
├── Goal: MAINTAINABLE, not just runnable
│
├── Workflow
│ Plan mode (requirements) -> architecture (who owns what)
│ -> CLAUDE.md (lock principles) -> module by module -> debug + test
│
├── MV3 environments
│ ├── Service Worker (background.js): alarms, state, routing; NO DOM/cookies; can be killed
│ ├── Content Script (content.js): DOM + page cookies ONLY (thin shell)
│ └── Popup/Options (popup.js): UI + config; dies on close
│ -> setInterval OUT, chrome.alarms IN
│
├── Build order: db.js -> rules.js -> api.js -> scanner.js
│ -> rate-limiter.js -> background.js -> content.js + UI
│ ├── all storage via db.js, atomic read->merge->write
│ └── scanner.js uses dependency injection (sendReplyFn, fetchCommentsFn, logFn)
│
├── Rate limiter: normal -> slow -> slower -> paused (recovers on success)
│
├── Debug: SW logs in SW DevTools; CS logs in page DevTools; reload per environment
│
└── Refactor story: 1,211-line content.js -> 175 lines + modules
(more total lines, LESS cognitive overhead; migrate in tested steps)
manifest.json uses manifest_version 3, a Service Worker, and minimal permissions.content.js is a thin shell (DOM + cookies only); all logic lives in background.js and lib/; write the principles into CLAUDE.md.db.js first (5 structured keys, atomic writes), then rules.js, api.js, scanner.js (dependency injection), rate-limiter.js (4 levels), background.js, then UI.CLAUDE.md, build bottom-up one module at a time, and refactor in small tested
steps. Refactoring is about reducing cognitive overhead, not line count — and Claude
Code's reach goes well beyond web pages.
This teaching edition is based on the supplied April 2026, 2nd edition of Claude Code: The Complete Guide (§11, "Hands-on Project: Chrome Extension"). Manifest V3 details, Chrome APIs, file/line counts and the sample project reflect that edition and may change over time.