Skip to content

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

  1. 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.
  2. 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).
  3. 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.
  4. Every message handler checks the sender with isTrustedSender.
  5. 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.
  6. Rich text is Markdown sanitised with DOMPurify before render.
  7. 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.