"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."
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 readstab.url,tab.title,tab.favIconUrloff the activeTab query results (the moderate 'browsing history' install warning).scripting— needed because background.js callschrome.scripting.executeScriptas 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>inhost_permissionsAND incontent_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.