C.W.K.
Stream
Lesson 01 of 08 · published

Anchor 1 — Manifest Anatomy

~10 min · manifest, case-study, chromeembed

Level 0Extension Curious
0 XP0/54 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"ChromeEmbed v0.1 is 28 lines of manifest and four script files. Lesson 1 is the manifest — every field justified, every choice tied back to one of the prior tracks."
Pippa ChromeEmbed v0.1 in action — Pippa's chat panel docked on the right edge of a Chrome window, host page visible in the main viewport
Pippa ChromeEmbed v0.1 — the working prototype this Track 9 walks through, file by file.

The 28 Lines

The actual ChromeEmbed manifest, end to end:

{
  "manifest_version": 3,
  "name": "Pippa ChromeEmbed",
  "version": "0.1.0",
  "description": "The household, present on the current page.",
  "permissions": ["sidePanel", "activeTab", "storage", "tabs", "scripting"],
  "host_permissions": [
    "<all_urls>",
    "http://localhost:5173/*",
    "http://127.0.0.1:5173/*",
    "http://100.x.x.x:5173/*"
  ],
  "background": { "service_worker": "background.js" },
  "side_panel": { "default_path": "sidepanel.html" },
  "content_scripts": [{
    "matches": ["<all_urls>"],
    "js": ["content-script.js"],
    "run_at": "document_idle",
    "all_frames": true
  }],
  "action": { "default_popup": "popup.html" },
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self'; frame-src http://localhost:5173 http://127.0.0.1:5173 http://100.x.x.x:5173;"
  }
}

Permissions — Why Each One

  • sidePanel — required for chrome.sidePanel.* (Track 4).
  • activeTab — covers the user-gesture-triggered scripting calls so the SW can read the current page's selection without standing host access (Track 6 Lesson 4).
  • storage — declared for future use; v0.1 leans on the SW's in-memory Map. Cheap to keep.
  • tabs — needed because background.js reads tab.url, tab.title, tab.favIconUrl off the activeTab query results (the moderate 'browsing history' install warning).
  • scripting — needed because background.js calls chrome.scripting.executeScript as a fallback to read selections when content scripts haven't reported in (the all-frames path).

Host Permissions — The Localhost Set

Two layers do double duty:

  • <all_urls> in host_permissions AND in content_scripts.matches — necessary because ChromeEmbed wants to provide context from any page the user visits, not just specific origins.
  • Localhost and 127.0.0.1 — for development. The cwkPippa dev server runs at localhost:5173; the side panel's iframe loads from there.
  • 100.x.x.x (a Tailscale CGNAT host — replace with your own private VPN IP) — lets other devices on the same private network reach the dev server. ChromeEmbed runs on every Mac in the household; whichever one hosts cwkPippa at any moment is reachable via its Tailscale IP.

Note that these localhost / Tailscale URLs appear in TWO places: the manifest's host_permissions array (for network access) AND the extension_pages CSP's frame-src directive (for the iframe specifically). Removing them from either breaks the panel; Track 4 Lesson 5 walks the CSP detail.

The all_frames Choice

The content script is declared with all_frames: true. ChromeEmbed wants to capture selections and viewport text even when the user highlights something inside an iframe (an embedded YouTube comment, a Notion embed). Default all_frames: false would miss those. The trade-off: every iframe loads the content script, which costs memory; for a Pippa-grade household extension that's acceptable.

The action.default_popup Choice

The popup exists (popup.html is declared) but is a 6-line doorway (Lesson 4). The pattern action.default_popup: "popup.html" together with the popup's own chrome.sidePanel.open() + window.close() means: click the toolbar icon → popup briefly flashes → side panel opens → popup closes. The user sees one fluid 'click opens the panel' motion.

Tracks That Built Each Decision

  • Track 1 — manifest_version 3, name, version, the basics.
  • Track 2 — service_worker declaration.
  • Track 3 — content_scripts with all_frames.
  • Track 4 — sidePanel permission + side_panel.default_path + extension_pages CSP frame-src.
  • Track 5 — action.default_popup.
  • Track 6 — narrow permission set (no downloads, no notifications); the storage permission is overhead but reserved for v0.2.

None of this is novel; it's the first eight tracks composed into one working file.

The manifest is the contract. Every field justified by a feature, every permission as narrow as the feature allows. ChromeEmbed's 28 lines are the eight tracks made concrete.
Why version 0.1.0, not 1.0.0? ChromeEmbed names itself v0.1 deliberately — it's the first concrete embed surface in a larger framework story (PIPPA-EMBEDS.md). v1 marks the API surface that downstream embeds (Adobe, Mail, Calendar) will inherit; ChromeEmbed proves the shape and exposes the gaps before that contract calcifies.

Code

embeds/chrome/manifest.json — the actual ChromeEmbed v0.1 manifest·json
{
  "manifest_version": 3,
  "name": "Pippa ChromeEmbed",
  "version": "0.1.0",
  "description": "The household, present on the current page.",
  "permissions": ["sidePanel", "activeTab", "storage", "tabs", "scripting"],
  "host_permissions": [
    "<all_urls>",
    "http://localhost:5173/*",
    "http://127.0.0.1:5173/*",
    "http://100.x.x.x:5173/*"
  ],
  "background": { "service_worker": "background.js" },
  "side_panel": { "default_path": "sidepanel.html" },
  "content_scripts": [{
    "matches": ["<all_urls>"],
    "js": ["content-script.js"],
    "run_at": "document_idle",
    "all_frames": true
  }],
  "action": { "default_popup": "popup.html" },
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self'; frame-src http://localhost:5173 http://127.0.0.1:5173 http://100.x.x.x:5173;"
  }
}

External links

Exercise

Open the real ChromeEmbed manifest at cwkPippa/embeds/chrome/manifest.json (if you have access to that repo) or copy the code block as your starting point. Walk each field and label it with the track that introduced it. Try one modification: remove the localhost entries from host_permissions AND the matching frame-src, then load the extension and try to open the side panel — confirm the iframe fails with a CSP error in the panel's DevTools console. Restore the entries to fix. The lesson: every field of the manifest does work, and removing any one of them surfaces immediately when you try to use the corresponding feature.
Hint
If the panel still loads after removing the localhost entries, you might have the production cwkPippa URL elsewhere — also check whether sidepanel.html's iframe src points at localhost or somewhere else. If you don't have cwkPippa repo access, just take the manifest as written; the lesson works on the snippet alone. The Track 9 lessons are read-and-reason exercises, not load-and-run; the goal is mental-model alignment, not local re-deployment of someone else's prototype.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.