Skip to content
C.W.K.
Stream
Lesson 01 of 10 · published

Anchor 1 — Manifest Anatomy

~10 min · manifest, case-study, chromeembed, v0.2.1

Level 0Extension Curious
0 XP0/56 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;"
  }
}
v0.2.1 checkpoint — embeds/chrome/manifest.json — current v0.2.1 shape (abridged)·json
{
  "manifest_version": 3,
  "name": "Pippa ChromeEmbed",
  "version": "0.2.1",
  "permissions": ["sidePanel", "activeTab", "storage", "tabs", "scripting", "clipboardWrite"],
  "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>"],
    "exclude_matches": [
      "http://localhost:5173/embed/*",
      "http://127.0.0.1:5173/embed/*",
      "http://100.x.x.x:5173/embed/*"
    ],
    "js": ["pippa-hosts.js", "content-script.js"],
    "run_at": "document_idle",
    "all_frames": true
  }],
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self'; connect-src http://localhost:5173 http://127.0.0.1:5173 http://100.x.x.x:5173; frame-src http://localhost:5173 http://127.0.0.1:5173 http://100.x.x.x:5173;"
  }
}

External links

Exercise

Open the live cwkPippa/embeds/chrome/manifest.json and compare it field by field with this abridged block. Trace every repeated frontend origin into pippa-hosts.js, host_permissions, exclude_matches, and both CSP directives. In a disposable copy, remove one origin from connect-src and confirm the frontend probe fails; restore it before reloading the real extension.
Hint
A host change is complete only when JavaScript and all three manifest surfaces agree. frame-src controls iframe loading, connect-src controls the reachability probe, and exclude_matches prevents self-capture.

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.