Package map and rules
Packages
flowchart TB
subgraph ext["apps/extension"]
bg["background<br/>(service worker)"]
cs["content gate"]
pl["player, picker,<br/>frame agent"]
ui["studio, popup, options"]
end
subgraph pure["No chrome.* allowed"]
loc["locator"]
eng["engine"]
ren["renderer"]
thm["theme"]
end
sch["schema"]
sto["store"]
msg["messaging"]
eng --> loc
eng --> sch
loc --> sch
ren --> sch
ren --> thm
thm --> sch
sto --> sch
msg --> sch
pl --> eng
pl --> ren
pl --> loc
ui --> sto
bg --> sto
bg --> msg
cs --> msg
style pure fill:#fde8e8,stroke:#b42318
style sch fill:#e0f2f1,stroke:#00796b
style sto fill:#e0f2f1,stroke:#00796b
Companion note
| Node |
What it is |
schema |
Zod schemas, types, factories, migrations. Exports the JSON Schema |
store |
The ContentStore interface, LocalStore (IndexedDB) and the bundle zip codec |
messaging |
The typed message contract, isTrustedSender, originMatches |
locator |
Capture, fingerprint, resolve. Pure DOM code |
engine |
Path and condition matching, trigger arming, eligibility, schedule, validation, strings |
renderer |
Preact components, shadow-root host, top-layer handling |
theme |
Theme tokens, extraction, CSS-variable allowlist |
bg |
Service worker: handlers, injection, content script registration, frame registry |
cs |
The content gate (about 2 kB) |
pl |
Player, picker and frame agent, injected on demand |
ui |
Studio side panel, popup, options page |
| Flow |
Meaning |
| Arrows |
"depends on". eng --> loc means the engine imports the locator |
| Red box |
Packages that must not call chrome.* or browser.* |
Rules
- No
chrome.* in locator, engine, renderer, theme. These four are pure and run in unit tests (jsdom or happy-dom) without an extension host. ESLint enforces it with no-restricted-globals on chrome for those four packages. The extension app is the only adapter to browser APIs.
ContentStore is the only content access. The studio and the service worker read and write content through the interface. LocalStore is the single implementation today. This is the seam for a remote store (extension seams).
- No remote code. Nothing is fetched and executed. Extension pages use
script-src 'self'; object-src 'self'. Every injected script is a file in the package.
- Every message handler checks the sender with
isTrustedSender.
- The content script is Zod-free and held to a gzip budget of 15 kB by
scripts/check-bundle-size.mjs (run by pnpm build). Decision 5 records about 1.4 kB.
- Rich text is Markdown sanitised with DOMPurify before render.
- The player's shadow root is closed in store builds and open only in the dev flavour.
Gotchas
- The lint rule bans the
chrome global only. It does not stop browser (the WXT alias) or an import of wxt/browser, so review still matters.
- The tests that exercise
renderer geometry run in jsdom with mocked rectangles, so layout bugs show up only in the Playwright suite.