@zigrivers/scaffold 3.8.0 → 3.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +73 -8
  2. package/content/knowledge/browser-extension/browser-extension-architecture.md +195 -0
  3. package/content/knowledge/browser-extension/browser-extension-content-scripts.md +264 -0
  4. package/content/knowledge/browser-extension/browser-extension-conventions.md +156 -0
  5. package/content/knowledge/browser-extension/browser-extension-cross-browser.md +229 -0
  6. package/content/knowledge/browser-extension/browser-extension-dev-environment.md +247 -0
  7. package/content/knowledge/browser-extension/browser-extension-manifest.md +220 -0
  8. package/content/knowledge/browser-extension/browser-extension-project-structure.md +183 -0
  9. package/content/knowledge/browser-extension/browser-extension-requirements.md +107 -0
  10. package/content/knowledge/browser-extension/browser-extension-security.md +202 -0
  11. package/content/knowledge/browser-extension/browser-extension-service-workers.md +265 -0
  12. package/content/knowledge/browser-extension/browser-extension-store-submission.md +155 -0
  13. package/content/knowledge/browser-extension/browser-extension-testing.md +270 -0
  14. package/content/knowledge/data-pipeline/data-pipeline-architecture.md +175 -0
  15. package/content/knowledge/data-pipeline/data-pipeline-batch-patterns.md +263 -0
  16. package/content/knowledge/data-pipeline/data-pipeline-conventions.md +176 -0
  17. package/content/knowledge/data-pipeline/data-pipeline-dev-environment.md +350 -0
  18. package/content/knowledge/data-pipeline/data-pipeline-orchestration.md +291 -0
  19. package/content/knowledge/data-pipeline/data-pipeline-project-structure.md +257 -0
  20. package/content/knowledge/data-pipeline/data-pipeline-quality.md +324 -0
  21. package/content/knowledge/data-pipeline/data-pipeline-requirements.md +145 -0
  22. package/content/knowledge/data-pipeline/data-pipeline-schema-management.md +295 -0
  23. package/content/knowledge/data-pipeline/data-pipeline-security.md +326 -0
  24. package/content/knowledge/data-pipeline/data-pipeline-streaming-patterns.md +280 -0
  25. package/content/knowledge/data-pipeline/data-pipeline-testing.md +406 -0
  26. package/content/knowledge/ml/ml-architecture.md +172 -0
  27. package/content/knowledge/ml/ml-conventions.md +209 -0
  28. package/content/knowledge/ml/ml-dev-environment.md +299 -0
  29. package/content/knowledge/ml/ml-experiment-tracking.md +285 -0
  30. package/content/knowledge/ml/ml-model-evaluation.md +256 -0
  31. package/content/knowledge/ml/ml-observability.md +253 -0
  32. package/content/knowledge/ml/ml-project-structure.md +216 -0
  33. package/content/knowledge/ml/ml-requirements.md +138 -0
  34. package/content/knowledge/ml/ml-security.md +188 -0
  35. package/content/knowledge/ml/ml-serving-patterns.md +243 -0
  36. package/content/knowledge/ml/ml-testing.md +301 -0
  37. package/content/knowledge/ml/ml-training-patterns.md +269 -0
  38. package/content/methodology/browser-extension-overlay.yml +82 -0
  39. package/content/methodology/data-pipeline-overlay.yml +70 -0
  40. package/content/methodology/ml-overlay.yml +70 -0
  41. package/dist/cli/commands/init.d.ts +13 -0
  42. package/dist/cli/commands/init.d.ts.map +1 -1
  43. package/dist/cli/commands/init.js +122 -2
  44. package/dist/cli/commands/init.js.map +1 -1
  45. package/dist/cli/commands/init.test.js +120 -0
  46. package/dist/cli/commands/init.test.js.map +1 -1
  47. package/dist/config/schema.d.ts +864 -48
  48. package/dist/config/schema.d.ts.map +1 -1
  49. package/dist/config/schema.js +53 -0
  50. package/dist/config/schema.js.map +1 -1
  51. package/dist/config/schema.test.js +166 -3
  52. package/dist/config/schema.test.js.map +1 -1
  53. package/dist/core/assembly/overlay-loader.test.js +33 -0
  54. package/dist/core/assembly/overlay-loader.test.js.map +1 -1
  55. package/dist/e2e/project-type-overlays.test.d.ts +2 -2
  56. package/dist/e2e/project-type-overlays.test.js +499 -33
  57. package/dist/e2e/project-type-overlays.test.js.map +1 -1
  58. package/dist/types/config.d.ts +10 -1
  59. package/dist/types/config.d.ts.map +1 -1
  60. package/dist/wizard/questions.d.ts +17 -1
  61. package/dist/wizard/questions.d.ts.map +1 -1
  62. package/dist/wizard/questions.js +75 -1
  63. package/dist/wizard/questions.js.map +1 -1
  64. package/dist/wizard/questions.test.js +167 -0
  65. package/dist/wizard/questions.test.js.map +1 -1
  66. package/dist/wizard/wizard.d.ts +13 -0
  67. package/dist/wizard/wizard.d.ts.map +1 -1
  68. package/dist/wizard/wizard.js +17 -1
  69. package/dist/wizard/wizard.js.map +1 -1
  70. package/package.json +1 -1
package/README.md CHANGED
@@ -29,7 +29,7 @@ Either way, Scaffold constructs the prompt and the target AI tool does the work.
29
29
 
30
30
  **Assembly engine** — At execution time, Scaffold builds a 7-section prompt from: system metadata, the meta-prompt, knowledge base entries, project context (artifacts from prior steps), methodology settings, layered instructions, and depth-specific execution guidance.
31
31
 
32
- **Knowledge base** — 158 domain expertise entries in `content/knowledge/` organized in thirteen categories (core, product, review, validation, finalization, execution, tools, game, web-app, backend, cli, library, mobile-app) covering testing strategy, domain modeling, API design, security best practices, eval craft, TDD execution, task claiming, worktree management, release management, rendering strategies, data stores, CLI patterns, game engines, library bundling, mobile deployment, and more. These get injected into prompts based on each step's `knowledge-base` frontmatter field. Knowledge files with a `## Deep Guidance` section are optimized for CLI assembly — only the deep guidance content is loaded, avoiding redundancy with the prompt text. Teams can add project-local overrides in `.scaffold/knowledge/` that layer on top of the global entries.
32
+ **Knowledge base** — 194 domain expertise entries in `content/knowledge/` organized in sixteen categories (core, product, review, validation, finalization, execution, tools, game, web-app, backend, cli, library, mobile-app, data-pipeline, ml, browser-extension) covering testing strategy, domain modeling, API design, security best practices, eval craft, TDD execution, task claiming, worktree management, release management, rendering strategies, data stores, CLI patterns, game engines, library bundling, mobile deployment, batch and streaming pipelines, model training and serving, browser extension manifests and service workers, and more. These get injected into prompts based on each step's `knowledge-base` frontmatter field. Knowledge files with a `## Deep Guidance` section are optimized for CLI assembly — only the deep guidance content is loaded, avoiding redundancy with the prompt text. Teams can add project-local overrides in `.scaffold/knowledge/` that layer on top of the global entries.
33
33
 
34
34
  **Methodology presets** — Three built-in presets control which steps run and how deep the analysis goes:
35
35
  - **deep** (depth 5) — all steps enabled, exhaustive analysis
@@ -368,7 +368,7 @@ Every `scaffold init` wizard question can be answered via CLI flags, making scaf
368
368
  | `--depth` | 1-5 | Custom methodology depth (requires `--methodology custom`) |
369
369
  | `--adapters` | comma-sep | AI adapters: claude-code, codex, gemini |
370
370
  | `--traits` | comma-sep | Project traits: web, mobile |
371
- | `--project-type` | string | web-app, mobile-app, backend, cli, library, game |
371
+ | `--project-type` | string | web-app, mobile-app, backend, cli, library, game, data-pipeline, ml, browser-extension |
372
372
  | `--auto` | boolean | Non-interactive mode (uses Zod defaults for unset flags) |
373
373
 
374
374
  #### Web-App Config Flags (require `--project-type web-app` or auto-set it)
@@ -417,6 +417,34 @@ Every `scaffold init` wizard question can be answered via CLI flags, making scaf
417
417
  | `--mobile-offline` | string | none, cache, offline-first |
418
418
  | `--mobile-push-notifications` | boolean | `--mobile-push-notifications` / `--no-mobile-push-notifications` |
419
419
 
420
+ #### Data Pipeline Config Flags (require `--project-type data-pipeline` or auto-set it)
421
+
422
+ | Flag | Type | Values |
423
+ |------|------|--------|
424
+ | `--pipeline-processing` | string | batch, streaming, hybrid |
425
+ | `--pipeline-orchestration` | string | none, dag-based, event-driven, scheduled |
426
+ | `--pipeline-quality` | string | none, validation, testing, observability |
427
+ | `--pipeline-schema` | string | none, schema-registry, contracts |
428
+ | `--pipeline-catalog` | boolean | `--pipeline-catalog` / `--no-pipeline-catalog` |
429
+
430
+ #### ML Config Flags (require `--project-type ml` or auto-set it)
431
+
432
+ | Flag | Type | Values |
433
+ |------|------|--------|
434
+ | `--ml-phase` | string | training, inference, both |
435
+ | `--ml-model-type` | string | classical, deep-learning, llm |
436
+ | `--ml-serving` | string | none, batch, realtime, edge |
437
+ | `--ml-experiment-tracking` | boolean | `--ml-experiment-tracking` / `--no-ml-experiment-tracking` |
438
+
439
+ #### Browser Extension Config Flags (require `--project-type browser-extension` or auto-set it)
440
+
441
+ | Flag | Type | Values |
442
+ |------|------|--------|
443
+ | `--ext-manifest` | string | 2, 3 |
444
+ | `--ext-ui-surfaces` | comma-sep | popup, options, newtab, devtools, sidepanel |
445
+ | `--ext-content-script` | boolean | `--ext-content-script` / `--no-ext-content-script` |
446
+ | `--ext-background-worker` | boolean | `--ext-background-worker` / `--no-ext-background-worker` |
447
+
420
448
  #### Game Config Flags (require `--project-type game` or auto-set it)
421
449
 
422
450
  | Flag | Type | Values |
@@ -439,9 +467,9 @@ Every `scaffold init` wizard question can be answered via CLI flags, making scaf
439
467
 
440
468
  - **Flag > auto > interactive**: Flags always take highest precedence. `--auto --engine unreal` uses defaults for everything except engine.
441
469
  - **Partial flags + interactive**: Provide some flags and the wizard asks only the remaining questions. `scaffold init --project-type game --engine unreal` prompts interactively for multiplayer, platforms, etc.
442
- - **Type-specific flags auto-set project type**: `--engine unity` automatically sets `--project-type game`, `--web-rendering ssr` sets `--project-type web-app`, `--backend-api-style rest` sets `--project-type backend`, `--cli-interactivity hybrid` sets `--project-type cli`, `--lib-visibility public` sets `--project-type library`, `--mobile-platform ios` sets `--project-type mobile-app`. Error if conflicting type.
443
- - **Cannot mix flag families**: `--web-rendering ssr --backend-api-style rest` is an error. Each flag family is exclusive.
444
- - **Validation**: `--depth` requires `--methodology custom`. `--online-services` requires `--multiplayer online` or `hybrid`. SSR/hybrid rendering is incompatible with static deploy target. Session auth requires server state (not static).
470
+ - **Type-specific flags auto-set project type**: `--engine unity` automatically sets `--project-type game`, `--web-rendering ssr` sets `--project-type web-app`, `--backend-api-style rest` sets `--project-type backend`, `--cli-interactivity hybrid` sets `--project-type cli`, `--lib-visibility public` sets `--project-type library`, `--mobile-platform ios` sets `--project-type mobile-app`, `--pipeline-processing batch` sets `--project-type data-pipeline`, `--ml-phase training` sets `--project-type ml`, `--ext-manifest 3` sets `--project-type browser-extension`. Error if conflicting type.
471
+ - **Cannot mix flag families**: `--web-rendering ssr --backend-api-style rest` is an error. Each flag family (`--web-*`, `--backend-*`, `--cli-*`, `--lib-*`, `--mobile-*`, `--pipeline-*`, `--ml-*`, `--ext-*`, game) is exclusive.
472
+ - **Validation**: `--depth` requires `--methodology custom`. `--online-services` requires `--multiplayer online` or `hybrid`. SSR/hybrid rendering is incompatible with static deploy target. Session auth requires server state (not static). ML inference projects must specify a serving pattern. Browser extensions must declare at least one capability (UI surface, content script, or background worker).
445
473
 
446
474
  #### CI Examples
447
475
 
@@ -493,6 +521,35 @@ scaffold init --auto --methodology deep --project-type mobile-app \
493
521
  scaffold init --auto --methodology mvp --project-type mobile-app \
494
522
  --mobile-platform ios --mobile-distribution private
495
523
 
524
+ # Streaming data pipeline with event-driven orchestration
525
+ scaffold init --auto --methodology deep --project-type data-pipeline \
526
+ --pipeline-processing streaming --pipeline-orchestration event-driven \
527
+ --pipeline-quality observability --pipeline-schema schema-registry
528
+
529
+ # Batch ETL pipeline with DAG orchestration
530
+ scaffold init --auto --methodology mvp --project-type data-pipeline \
531
+ --pipeline-processing batch --pipeline-orchestration dag-based \
532
+ --pipeline-quality validation
533
+
534
+ # LLM inference service with realtime serving
535
+ scaffold init --auto --methodology deep --project-type ml \
536
+ --ml-phase inference --ml-model-type llm --ml-serving realtime
537
+
538
+ # Classical ML training pipeline (no serving)
539
+ scaffold init --auto --methodology mvp --project-type ml \
540
+ --ml-phase training --ml-model-type classical \
541
+ --no-ml-experiment-tracking
542
+
543
+ # MV3 browser extension with popup and content script
544
+ scaffold init --auto --methodology deep --project-type browser-extension \
545
+ --ext-manifest 3 --ext-ui-surfaces popup,options \
546
+ --ext-content-script --ext-background-worker
547
+
548
+ # Devtools-only browser extension
549
+ scaffold init --auto --methodology mvp --project-type browser-extension \
550
+ --ext-manifest 3 --ext-ui-surfaces devtools \
551
+ --no-ext-content-script
552
+
496
553
  # Multiplayer mobile game with Unity
497
554
  scaffold init --project-type game --methodology deep --auto \
498
555
  --engine unity --multiplayer online --target-platforms ios,android \
@@ -519,7 +576,7 @@ Scaffold supports **project-type overlays** — domain-specific knowledge and pi
519
576
 
520
577
  - **Injects domain knowledge** into existing pipeline steps (e.g., SSR caching strategies into `tech-stack`, API pagination patterns into `coding-standards`)
521
578
 
522
- The game overlay additionally adjusts step enablement, remaps artifact references, and adds dependency overrides (because game development has fundamentally different artifacts). The web-app, backend, CLI, library, and mobile-app overlays are **knowledge-only** — they inject domain expertise into existing steps without changing which steps run or how they depend on each other.
579
+ The game overlay additionally adjusts step enablement, remaps artifact references, and adds dependency overrides (because game development has fundamentally different artifacts). The web-app, backend, CLI, library, mobile-app, data-pipeline, ML, and browser-extension overlays are **knowledge-only** — they inject domain expertise into existing steps without changing which steps run or how they depend on each other.
523
580
 
524
581
  Overlays are composable with methodology presets. An MVP web-app gets fewer steps at lower depth; a deep backend project gets exhaustive analysis of every architectural decision.
525
582
 
@@ -530,6 +587,9 @@ Overlays are composable with methodology presets. An MVP web-app gets fewer step
530
587
  | `cli` | `cli-overlay.yml` | 10 entries (argument parsing, config management, output formatting, distribution, testing, error handling) | Interactivity model, distribution channels, structured output |
531
588
  | `library` | `library-overlay.yml` | 12 entries (API design, bundling, type definitions, versioning, documentation, testing, security) | Visibility, runtime target, bundle format, type definitions, documentation level |
532
589
  | `mobile-app` | `mobile-app-overlay.yml` | 12 entries (architecture, offline patterns, push notifications, deployment, distribution, testing, security) | Platform, distribution model, offline support, push notifications |
590
+ | `data-pipeline` | `data-pipeline-overlay.yml` | 12 entries (architecture, batch and streaming patterns, orchestration, schema management, quality, testing, security) | Processing model, orchestration, data quality strategy, schema management, data catalog |
591
+ | `ml` | `ml-overlay.yml` | 12 entries (architecture, training and serving patterns, experiment tracking, model evaluation, observability, testing, security) | Project phase, model type, serving pattern, experiment tracking |
592
+ | `browser-extension` | `browser-extension-overlay.yml` | 12 entries (architecture, manifest configuration, service workers, content scripts, cross-browser, store submission, testing, security) | Manifest version, UI surfaces, content script, background worker |
533
593
  | `game` | `game-overlay.yml` | 24 entries (engines, networking, audio, VR/AR, economy, save systems, certification) | Engine, multiplayer, platforms, economy, narrative, and 6 more |
534
594
 
535
595
  ### Game Development
@@ -1224,7 +1284,7 @@ scaffold dashboard
1224
1284
 
1225
1285
  ## Knowledge System
1226
1286
 
1227
- Scaffold ships with 134 domain expertise entries organized in eleven categories:
1287
+ Scaffold ships with 194 domain expertise entries organized in sixteen categories:
1228
1288
 
1229
1289
  - **core/** (26 entries) — eval craft, testing strategy, domain modeling, API design, database design, system architecture, ADR craft, security best practices, operations, task decomposition, user stories, UX specification, design system tokens, user story innovation, AI memory management, coding conventions, tech stack selection, project structure patterns, task tracking, CLAUDE.md patterns, multi-model review dispatch, review step template, dev environment, git workflow patterns, automated review tooling, vision craft
1230
1290
  - **product/** (5 entries) — PRD craft, PRD innovation, gap analysis, vision craft, vision innovation
@@ -1237,6 +1297,11 @@ Scaffold ships with 134 domain expertise entries organized in eleven categories:
1237
1297
  - **web-app/** (17 entries) — rendering strategies (SSR/SSG/SPA), state management, authentication, deploy targets, real-time patterns, PWA, performance, security, testing, session patterns, UX patterns, caching, API integration, accessibility
1238
1298
  - **backend/** (14 entries) — API design patterns, data store selection, authentication mechanisms, messaging/event systems, observability, deploy strategies, caching, rate limiting, error handling, database migrations, testing, security
1239
1299
  - **cli/** (10 entries) — argument parsing, config management, output formatting, distribution channels, testing patterns, error handling, plugin architecture, shell integration, structured output, interactive prompts
1300
+ - **library/** (12 entries) — visibility (public/internal), bundle formats (ESM/CJS/dual), type definitions, documentation levels, semver discipline, supply chain security, runtime targets
1301
+ - **mobile-app/** (12 entries) — platform-specific patterns (iOS/Android/cross-platform), distribution models (app store/enterprise), offline support, push notifications, mobile testing
1302
+ - **data-pipeline/** (12 entries) — batch/streaming/hybrid patterns, orchestration (DAG/event-driven/scheduled), data quality, schema management, lineage, pipeline testing
1303
+ - **ml/** (12 entries) — training and inference patterns, model types (classical/deep-learning/llm), serving patterns, experiment tracking, model evaluation, MLOps observability
1304
+ - **browser-extension/** (12 entries) — Manifest V3, content scripts, service workers, cross-browser compatibility, extension security, store submission
1240
1305
 
1241
1306
  Each pipeline step declares which knowledge entries it needs in its frontmatter. The assembly engine injects them automatically. Knowledge files with a `## Deep Guidance` section are optimized for the CLI — only the deep guidance content is loaded into the assembled prompt, skipping the summary to avoid redundancy with the prompt text.
1242
1307
 
@@ -1442,7 +1507,7 @@ All build inputs live under `content/`:
1442
1507
  content/
1443
1508
  ├── pipeline/ # 60 meta-prompts organized by 16 phases (phases 0-15, including build)
1444
1509
  ├── tools/ # 10 tool meta-prompts (stateless, category: tool)
1445
- ├── knowledge/ # 61 domain expertise entries (core, product, review, validation, finalization, execution, tools)
1510
+ ├── knowledge/ # 194 domain expertise entries (core, product, review, validation, finalization, execution, tools, game, web-app, backend, cli, library, mobile-app, data-pipeline, ml, browser-extension)
1446
1511
  ├── methodology/ # 3 YAML presets (deep, mvp, custom)
1447
1512
  └── skills/ # Skill templates with {{markers}} for multi-platform resolution (includes mmr)
1448
1513
  ```
@@ -0,0 +1,195 @@
1
+ ---
2
+ name: browser-extension-architecture
3
+ description: Component isolation across content scripts, background service workers, and popup pages; message passing patterns; and state synchronization strategies
4
+ topics: [browser-extension, architecture, message-passing, state-synchronization, service-worker, content-scripts]
5
+ ---
6
+
7
+ Browser extension architecture is fundamentally different from web app architecture because the application is split across multiple isolated execution environments that cannot share memory directly. Content scripts run inside host pages but in an isolated JavaScript world. Service workers run in a separate context that is terminated and re-created between events. Popup pages are ephemeral — they exist only while the popup is open. These constraints drive every architectural decision: communication is via message passing, state must be externalized to `chrome.storage`, and every component must tolerate being initialized from scratch at any time.
8
+
9
+ ## Summary
10
+
11
+ Browser extensions have three primary execution contexts: content scripts (run in page context, DOM access, restricted API), background service worker (runs in extension context, full API access, no DOM, terminated between events), and popup/options pages (full HTML pages, full API access, ephemeral lifecycle). Communication between contexts is via `chrome.runtime.sendMessage` (one-shot) or `chrome.runtime.connect` (persistent port). State is synchronized via `chrome.storage` as the single source of truth, with `chrome.storage.onChanged` listeners propagating changes to interested contexts.
12
+
13
+ ## Deep Guidance
14
+
15
+ ### Execution Context Isolation
16
+
17
+ Understanding the isolation boundaries is the foundation of extension architecture. Bugs caused by violating these boundaries are subtle and hard to diagnose.
18
+
19
+ **Content scripts:**
20
+ - Execute in the context of a web page but in an isolated JavaScript world — they share the DOM with the page but do not share the JavaScript scope.
21
+ - Can read and modify the DOM.
22
+ - Can communicate with the host page via `window.postMessage` (crosses the world boundary).
23
+ - Can call a limited subset of `chrome.*` APIs: `chrome.runtime.sendMessage`, `chrome.runtime.connect`, `chrome.storage`, `chrome.i18n`.
24
+ - Cannot call `chrome.tabs`, `chrome.windows`, `chrome.browsingData`, or most other privileged APIs.
25
+ - Multiple content scripts run as separate injected scripts — they share the isolated world and can communicate via DOM events or shared global state if needed.
26
+
27
+ **Background service worker:**
28
+ - Runs in the extension context — fully isolated from any web page.
29
+ - Has access to the full `chrome.*` API surface.
30
+ - Has no DOM — `document` and `window` are undefined.
31
+ - Is event-driven: Chrome terminates the service worker when it is idle and restarts it when an event arrives. Never rely on in-memory state surviving between event handlers.
32
+ - All persistent state must live in `chrome.storage.local`, `chrome.storage.sync`, or `IndexedDB`.
33
+
34
+ **Popup and options pages:**
35
+ - Full HTML pages loaded in the extension context.
36
+ - Have full `chrome.*` API access and full DOM access.
37
+ - Popup: exists only while the popup window is open. Closing the popup destroys all JavaScript state. Do not use the popup as a state manager.
38
+ - Options page: persistent as long as the tab is open, but still an ephemeral page that should load its state from `chrome.storage` on mount.
39
+
40
+ ### Message Passing Architecture
41
+
42
+ Message passing is the only way for isolated contexts to communicate. Design a clear message flow before writing handlers.
43
+
44
+ **One-shot messages (chrome.runtime.sendMessage):**
45
+
46
+ ```typescript
47
+ // Sender (popup or content script)
48
+ const response = await chrome.runtime.sendMessage<RequestPayload, ResponsePayload>({
49
+ type: Messages.POPUP_GET_STATUS,
50
+ payload: undefined,
51
+ });
52
+
53
+ // Receiver (background service worker)
54
+ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
55
+ if (message.type === Messages.POPUP_GET_STATUS) {
56
+ getStatus().then(sendResponse);
57
+ return true; // Required for async sendResponse
58
+ }
59
+ });
60
+ ```
61
+
62
+ **Key rules for sendMessage:**
63
+ - The listener must `return true` if `sendResponse` will be called asynchronously. Failing to return `true` causes the message channel to close before the response arrives.
64
+ - Wrap all `sendMessage` calls in try/catch — if no listener is registered (e.g., the service worker has not started yet), Chrome throws `"Could not establish connection. Receiving end does not exist."`.
65
+ - One-shot messages are appropriate for request/response patterns: get current state, toggle a setting, trigger an action.
66
+
67
+ **Persistent connections (chrome.runtime.connect):**
68
+
69
+ ```typescript
70
+ // Content script — persistent port for streaming updates
71
+ const port = chrome.runtime.connect({ name: 'content-state-stream' });
72
+
73
+ port.onMessage.addListener((message) => {
74
+ applyStateUpdate(message);
75
+ });
76
+
77
+ port.onDisconnect.addListener(() => {
78
+ // Service worker was terminated or background closed the port
79
+ // Reconnect or degrade gracefully
80
+ });
81
+
82
+ // Background service worker
83
+ chrome.runtime.onConnect.addListener((port) => {
84
+ if (port.name === 'content-state-stream') {
85
+ // Store port reference for pushing updates
86
+ activePorts.add(port);
87
+ port.onDisconnect.addListener(() => {
88
+ activePorts.delete(port);
89
+ });
90
+ }
91
+ });
92
+ ```
93
+
94
+ Use persistent ports for: real-time updates pushed from background to content, long-lived streams (e.g., streaming API responses), and cases where the overhead of repeated `sendMessage` calls is unacceptable.
95
+
96
+ **Background-to-content communication:**
97
+
98
+ Sending messages from the background to a content script requires knowing the tab ID:
99
+
100
+ ```typescript
101
+ // Background → Content (requires tab ID)
102
+ await chrome.tabs.sendMessage(tabId, {
103
+ type: Messages.BG_INJECT_OVERLAY,
104
+ payload: { data },
105
+ });
106
+ ```
107
+
108
+ The background cannot send to a content script without a tab ID. Obtain it from `sender.tab.id` in an incoming message, from `chrome.tabs.query()`, or from the `chrome.tabs.onActivated` event.
109
+
110
+ ### State Synchronization Architecture
111
+
112
+ `chrome.storage` is the single source of truth for all extension state. Every context reads from and writes to storage; no context holds authoritative state in memory.
113
+
114
+ **Storage tiers:**
115
+
116
+ - `chrome.storage.sync` — Syncs across the user's browsers via their Google/Firefox account. Limited to 100 KB total, 8 KB per item. Use for user preferences and settings.
117
+ - `chrome.storage.local` — Local to the device. 10 MB default limit (can request 5 GB with `unlimitedStorage` permission). Use for cached data, large state objects, per-device state.
118
+ - `chrome.storage.session` — (Chrome 102+) — Cleared when the browser session ends. Use for ephemeral state that must survive service worker restarts within a session but not persist across browser restarts.
119
+
120
+ **Typed storage wrapper pattern:**
121
+
122
+ ```typescript
123
+ // src/shared/storage.ts
124
+ export const StorageKeys = {
125
+ ENABLED: 'enabled',
126
+ SITES_CONFIG: 'sitesConfig',
127
+ LAST_ACTIVE: 'lastActive',
128
+ } as const;
129
+
130
+ export interface ExtensionState {
131
+ [StorageKeys.ENABLED]: boolean;
132
+ [StorageKeys.SITES_CONFIG]: SiteConfig[];
133
+ [StorageKeys.LAST_ACTIVE]: number;
134
+ }
135
+
136
+ export async function getState(): Promise<Partial<ExtensionState>> {
137
+ return chrome.storage.sync.get(null) as Promise<Partial<ExtensionState>>;
138
+ }
139
+
140
+ export async function setState(partial: Partial<ExtensionState>): Promise<void> {
141
+ return chrome.storage.sync.set(partial);
142
+ }
143
+ ```
144
+
145
+ **Reactive state propagation with onChanged:**
146
+
147
+ ```typescript
148
+ // In popup or options page — react to storage changes in real time
149
+ chrome.storage.onChanged.addListener((changes, area) => {
150
+ if (area === 'sync' && changes[StorageKeys.ENABLED]) {
151
+ const newValue = changes[StorageKeys.ENABLED].newValue as boolean;
152
+ setUIEnabled(newValue);
153
+ }
154
+ });
155
+ ```
156
+
157
+ This pattern ensures all open popup/options pages reflect state changes made by background or content scripts without requiring explicit message passing.
158
+
159
+ ### Popup Architecture
160
+
161
+ The popup is stateless by design — it reads from storage on open and writes to storage on user interaction. The background reacts to storage changes.
162
+
163
+ **Anti-pattern:** Popup sends message to background requesting action → background performs action → background sends message back to popup. This creates a request/response round trip that duplicates what storage-driven reactivity provides for free.
164
+
165
+ **Correct pattern:** Popup writes to storage → background listens via `chrome.storage.onChanged` → background performs action → background writes result to storage → popup listens via `chrome.storage.onChanged` → popup updates UI.
166
+
167
+ ### Service Worker Lifecycle Management
168
+
169
+ Because the service worker is terminated when idle, all setup that must survive restarts belongs in `chrome.storage`, not in module-level variables.
170
+
171
+ **Install handler — first-run initialization:**
172
+
173
+ ```typescript
174
+ chrome.runtime.onInstalled.addListener(async ({ reason }) => {
175
+ if (reason === chrome.runtime.OnInstalledReason.INSTALL) {
176
+ // Set default state
177
+ await chrome.storage.sync.set({
178
+ [StorageKeys.ENABLED]: true,
179
+ [StorageKeys.SITES_CONFIG]: [],
180
+ });
181
+ // Open onboarding tab
182
+ await chrome.tabs.create({ url: chrome.runtime.getURL('options/index.html') });
183
+ }
184
+ if (reason === chrome.runtime.OnInstalledReason.UPDATE) {
185
+ // Handle migration if schema changed
186
+ await migrateStorage();
187
+ }
188
+ });
189
+ ```
190
+
191
+ **Keeping the service worker active when needed:**
192
+
193
+ For operations that must not be interrupted by service worker termination (e.g., a long-running fetch), use the Chrome alarms API to schedule a "keepalive" alarm, or use `chrome.storage.session` to track whether work is in progress and re-initiate it on service worker restart.
194
+
195
+ Never use `setInterval` or `setTimeout` for recurring background work — they do not survive service worker termination. Use `chrome.alarms` instead.
@@ -0,0 +1,264 @@
1
+ ---
2
+ name: browser-extension-content-scripts
3
+ description: DOM manipulation from content scripts, isolated worlds, CSS injection, and communicating with the host page via postMessage
4
+ topics: [browser-extension, content-scripts, dom-manipulation, isolated-worlds, css-injection, postmessage]
5
+ ---
6
+
7
+ Content scripts are the extension's interface with the web page. They run inside the page's DOM but in an isolated JavaScript world — they see the same HTML and can manipulate the same elements, but they cannot access the page's JavaScript variables or prototype chain without explicitly crossing the world boundary. Understanding this isolation is essential for writing content scripts that are both functional and secure.
8
+
9
+ ## Summary
10
+
11
+ Content scripts execute in an isolated JavaScript world by default (ISOLATED world): they have DOM access but no access to the page's JS globals. CSS is injected via manifest declarations or `chrome.scripting.insertCSS()`. Communication with the host page uses `window.postMessage()` (crosses the world boundary) with origin validation. Communication with the background service worker uses `chrome.runtime.sendMessage()`. Minimize DOM manipulation, use `MutationObserver` for dynamic pages, and always clean up injected elements and observers when the extension is disabled.
12
+
13
+ ## Deep Guidance
14
+
15
+ ### Isolated Worlds
16
+
17
+ The isolated world is the security boundary between extension code and page code. It has important implications:
18
+
19
+ **What content scripts can access:**
20
+ - The full DOM (`document`, `window.location`, `document.querySelector`, all DOM methods).
21
+ - The `window` object's properties that are reflected from the DOM (e.g., `window.location`, `window.document`).
22
+ - `window.addEventListener` — content scripts and page scripts can both add listeners to the same DOM events.
23
+
24
+ **What content scripts cannot access:**
25
+ - JavaScript variables defined by the page script (`window.myApp`, `window.React`, page-defined globals).
26
+ - DOM properties set by page scripts that are not reflected in the HTML (e.g., a React component's state stored in a closure).
27
+ - Custom properties set on DOM elements by page scripts (e.g., `element._reactInternals` — the element is accessible but the custom property is set in the page world, not visible in the isolated world).
28
+
29
+ **MAIN world execution** (when you need page-world access):
30
+
31
+ ```json
32
+ // manifest.json
33
+ "content_scripts": [
34
+ {
35
+ "matches": ["https://example.com/*"],
36
+ "js": ["content-main-world.js"],
37
+ "world": "MAIN"
38
+ }
39
+ ]
40
+ ```
41
+
42
+ Or programmatically:
43
+
44
+ ```typescript
45
+ await chrome.scripting.executeScript({
46
+ target: { tabId },
47
+ func: () => {
48
+ // This runs in the page's JavaScript world
49
+ return window.myPageGlobal?.someValue;
50
+ },
51
+ world: 'MAIN',
52
+ });
53
+ ```
54
+
55
+ Use MAIN world execution sparingly — it gives extension code full access to page variables, increasing the risk of interference and XSS if inputs are not sanitized.
56
+
57
+ ### DOM Manipulation Patterns
58
+
59
+ **Safe element injection:**
60
+
61
+ ```typescript
62
+ // Create a container that isolates extension styles from the page
63
+ function injectOverlay(): void {
64
+ // Check for duplicate injection
65
+ if (document.getElementById('my-ext-overlay')) return;
66
+
67
+ const container = document.createElement('div');
68
+ container.id = 'my-ext-overlay';
69
+ container.setAttribute('role', 'complementary');
70
+ container.setAttribute('aria-label', 'My Extension');
71
+
72
+ // Use Shadow DOM for style isolation
73
+ const shadow = container.attachShadow({ mode: 'closed' });
74
+
75
+ const style = document.createElement('style');
76
+ style.textContent = overlayCSS; // Inline CSS string
77
+
78
+ const content = document.createElement('div');
79
+ content.className = 'overlay-content';
80
+
81
+ shadow.appendChild(style);
82
+ shadow.appendChild(content);
83
+ document.body.appendChild(container);
84
+ }
85
+ ```
86
+
87
+ **Why Shadow DOM:** Shadow DOM provides style encapsulation. Extension styles do not leak into the host page, and host page styles do not affect extension UI. This is the correct approach for injecting interactive UI elements. If you only need to inject non-interactive content or annotations, direct DOM insertion is acceptable.
88
+
89
+ **Cleanup on disable:**
90
+
91
+ ```typescript
92
+ function cleanup(): void {
93
+ const overlay = document.getElementById('my-ext-overlay');
94
+ overlay?.remove();
95
+ observer?.disconnect();
96
+ window.removeEventListener('message', messageHandler);
97
+ }
98
+
99
+ // Listen for cleanup signal from background
100
+ chrome.runtime.onMessage.addListener((message) => {
101
+ if (message.type === Messages.BG_CLEAR_STATE) {
102
+ cleanup();
103
+ }
104
+ });
105
+ ```
106
+
107
+ Always implement cleanup. Users who disable the extension expect the page to return to its original state without requiring a refresh.
108
+
109
+ ### Handling Dynamic Pages (SPA and Infinite Scroll)
110
+
111
+ Modern web apps modify the DOM after initial load. `document_idle` injection runs once — if the relevant content loads asynchronously, the content script misses it.
112
+
113
+ **MutationObserver for dynamic content:**
114
+
115
+ ```typescript
116
+ function observeForTargetElements(): void {
117
+ const observer = new MutationObserver((mutations) => {
118
+ for (const mutation of mutations) {
119
+ for (const node of mutation.addedNodes) {
120
+ if (node.nodeType !== Node.ELEMENT_NODE) continue;
121
+ const el = node as Element;
122
+
123
+ if (el.matches('.target-class')) {
124
+ processElement(el);
125
+ }
126
+ // Also check descendants
127
+ el.querySelectorAll('.target-class').forEach(processElement);
128
+ }
129
+ }
130
+ });
131
+
132
+ observer.observe(document.body, {
133
+ childList: true,
134
+ subtree: true,
135
+ });
136
+ }
137
+ ```
138
+
139
+ **Performance consideration:** `MutationObserver` callbacks run synchronously after DOM mutations. Heavy work in the callback (complex selectors, network requests) will block the page's rendering. Defer expensive work:
140
+
141
+ ```typescript
142
+ const deferredWork = debounce((elements: Element[]) => {
143
+ elements.forEach(processElement);
144
+ }, 100);
145
+
146
+ const observer = new MutationObserver((mutations) => {
147
+ const targets = mutations
148
+ .flatMap(m => [...m.addedNodes])
149
+ .filter(n => n.nodeType === Node.ELEMENT_NODE)
150
+ .flatMap(el => [...(el as Element).querySelectorAll('.target-class')]);
151
+
152
+ if (targets.length > 0) deferredWork(targets);
153
+ });
154
+ ```
155
+
156
+ **URL change detection (for SPA navigation):**
157
+
158
+ SPAs change the URL via `history.pushState` without triggering a page load. Content scripts do not automatically re-run on SPA navigation:
159
+
160
+ ```typescript
161
+ let lastUrl = location.href;
162
+
163
+ const urlObserver = new MutationObserver(() => {
164
+ if (location.href !== lastUrl) {
165
+ lastUrl = location.href;
166
+ onNavigate(location.href);
167
+ }
168
+ });
169
+
170
+ urlObserver.observe(document, { subtree: true, childList: true });
171
+ ```
172
+
173
+ ### CSS Injection
174
+
175
+ **Method 1 — Manifest declaration (preferred for always-on styles):**
176
+
177
+ ```json
178
+ "content_scripts": [
179
+ {
180
+ "matches": ["https://example.com/*"],
181
+ "css": ["content.css"]
182
+ }
183
+ ]
184
+ ```
185
+
186
+ Injected stylesheets receive the lowest specificity among author styles. Host page styles with equal or higher specificity will override them. Use high-specificity selectors or `!important` (sparingly) on extension-injected styles that must not be overridden.
187
+
188
+ **Method 2 — Programmatic injection:**
189
+
190
+ ```typescript
191
+ // From background service worker
192
+ await chrome.scripting.insertCSS({
193
+ target: { tabId },
194
+ files: ['content.css'],
195
+ });
196
+
197
+ // Remove previously injected CSS
198
+ await chrome.scripting.removeCSS({
199
+ target: { tabId },
200
+ files: ['content.css'],
201
+ });
202
+ ```
203
+
204
+ Programmatic injection allows toggling styles on demand. Use it when the extension can be enabled/disabled per site.
205
+
206
+ **Method 3 — Inline style element (for dynamic styles):**
207
+
208
+ ```typescript
209
+ const style = document.createElement('style');
210
+ style.id = 'my-ext-styles';
211
+ style.textContent = generateDynamicCSS(userPreferences);
212
+ document.head.appendChild(style);
213
+ ```
214
+
215
+ ### Communication with the Host Page via postMessage
216
+
217
+ Content scripts and page scripts cannot call each other's functions directly due to world isolation. `window.postMessage` is the crossing mechanism.
218
+
219
+ **Sending from content script to page script:**
220
+
221
+ ```typescript
222
+ // Content script — send message to page
223
+ window.postMessage(
224
+ { source: 'my-extension', type: 'INIT', payload: { version: '1.0' } },
225
+ window.location.origin, // MUST restrict origin — never use '*' for extension messages
226
+ );
227
+ ```
228
+
229
+ **Receiving in page script:**
230
+
231
+ ```javascript
232
+ // Page script
233
+ window.addEventListener('message', (event) => {
234
+ // ALWAYS validate source origin
235
+ if (event.origin !== window.location.origin) return;
236
+ if (!event.data || event.data.source !== 'my-extension') return;
237
+
238
+ handleExtensionMessage(event.data);
239
+ });
240
+ ```
241
+
242
+ **Receiving in content script from page:**
243
+
244
+ ```typescript
245
+ // Content script — receive messages from page script
246
+ function messageHandler(event: MessageEvent): void {
247
+ if (event.origin !== window.location.origin) return;
248
+ if (!event.data || event.data.source !== 'my-page-app') return;
249
+
250
+ // Forward to background service worker
251
+ chrome.runtime.sendMessage({
252
+ type: Messages.CONTENT_PAGE_LOADED,
253
+ payload: event.data.payload,
254
+ });
255
+ }
256
+
257
+ window.addEventListener('message', messageHandler);
258
+ ```
259
+
260
+ **Security rules for postMessage:**
261
+ - Always specify the target origin — never use `'*'`. Malicious pages at other origins could otherwise intercept extension messages.
262
+ - Always validate `event.origin` in receivers.
263
+ - Always validate a `source` field on the message object to distinguish extension messages from other `postMessage` traffic on the page.
264
+ - Never pass sensitive data (auth tokens, PII) via `postMessage` — the host page receives these messages too.