// manifest.json — the whole list. No broad site access.
"permissions": [
"storage", // forms, prefs, history — in your profile
"activeTab", // only the tab you opened it on
"scripting", // one extraction fn, injected on demand
"contextMenus",
"sidePanel",
"identity", // Notion's own consent screen
"alarms" // retry a save made offline
],
"host_permissions": [
"https://api.notion.com/*"
],
"optional_permissions": ["notifications"]
// grab-notion/ — load this folder in Chrome
src/
├── core/ browser adapter, constants, errors
├── storage/ schema, defaults, migrations
├── notion/ API client, auth, property mapping
├── extraction/ injected reader + picker, screenshots
├── forms/ save pipeline, block builder, queue
├── app/ the shared shell and every screen
├── background/ MV3 service worker
├── popup/ mounts the shell at 396px
├── sidepanel/ mounts the same shell, resizable
└── fullpage/ mounts the same shell, no ceiling
// one shell, four surfaces — they cannot drift
$ npm test
✓ storage/migrate keeps an existing form intact
✓ forms/blocks default layout is byte-identical
✓ notion/mapping rejects date → checkbox at build time
✓ forms/queue redrains offline saves exactly once
✓ extraction/read refuses chrome:// honestly
✓ package no node_modules in the zip
… 174 more
180 passing 0 skipped