{
  "markdown": "# pwa-debug-layer\n\nAn **AI-native debugging layer for PWAs and modern web apps**. It lets an AI agent (e.g. Claude Code via MCP) **see and act on your live, logged-in browser** the way a developer with full DevTools open would — DOM, console, network, framework state, store state, service workers, caches, and direct interaction — as structured data the model consumes natively.\n\nIt's built for the questions developers actually search for and that Chrome DevTools makes you assemble by hand: *why won't my service worker update? why is my cache stale? why won't my PWA install? why does this component have the wrong state?* — answered by an agent reading the runtime directly, against **your real browser profile** (extensions, auth, and all), not a sterile automated tab.\n\n### What it answers\n\nThe PWA failures developers actually search for, read straight from your live runtime:\n\n- **Why won't my service worker update? Why are some users on old code?** — SW lifecycle with waiting-vs-active versions, plus an update-propagation / version-skew analyzer.\n- **Why is my cache stale? Why are chunks 404ing after a deploy?** — CacheStorage contents with age, and a cached-HTML-vs-JS skew check.\n- **Why won't my PWA install?** — structured installability diagnostics with per-gap remediation, not just \"manifest invalid.\"\n- **What's actually in IndexedDB / localStorage right now?** — live storage inspection (*inspect IndexedDB live*).\n- **What can this browser actually do?** — a live capability matrix (Push / Background Sync / Periodic Sync / Badging / File System Access / Window Controls Overlay).\n\nAll read from your real, logged-in profile — service-worker, cache, and extension state included — which `chrome-devtools-mcp`'s sterile automated Chrome can't see.\n\nThe goal is to eliminate the \"user is the AI's eyes and hands\" loop. Today, debugging a PWA with AI usually means the human copy/pastes DOM snippets, describes console errors, screenshots UI state, and hand-executes clicks. This project replaces that with direct, structured access.\n\n> **Status: working on Linux.** The full MCP→IPC→native-host→service-worker→page-world round-trip is live, and a broad debugging surface is shipped:\n> - **Capture** — console / network / error / DOM-mutation / lifecycle, with persistent ring buffers + disk spill.\n> - **Framework introspection** — **React, Vue, Svelte, and Solid** (component/element trees, state, find-by-text/role).\n> - **Store introspection** — **Redux, Zustand, Pinia, and Jotai** (read, subscribe, dispatch).\n> - **Interaction + touch gestures** — click, fill, submit, hover, focus/blur, select, key/type, **drag, scroll, swipe, tap, double-tap, long-press, pinch**.\n> - **Library-popup capture/replay** — WalletConnect / SDK modals: record, replay, tail, failure correlation.\n> - **Replay & source maps** — rrweb `session_record`/`session_replay`, `source_map_resolve`.\n> - **Browser launcher** — one-call `pdl_launch_browser` with `chrome-devtools-mcp` coexistence.\n> - **PWA Runtime Diagnostics** — service-worker lifecycle + versions, CacheStorage contents + age, installability gaps, a live capability matrix, IndexedDB/web-storage inspection, update-propagation / version-skew analysis, and a one-shot runtime-state snapshot.\n>\n> **Verified on Linux** (the full suite live-tested against a real PWA). macOS/Windows code paths are implemented with unit coverage but still need real-machine retest ([help wanted](#help-wanted-macos--windows-verification)). Firefox is not supported (it doesn't speak CDP).\n>\n> ⚠️ **macOS / Windows users, beware:** these platforms have **never been run on real hardware** — only unit-tested with injected fakes. Expect rough edges (browser detection, profile paths, native-messaging registration, system-default resolution). Please [open an issue](../../issues) with the failing command and its output — bug reports from real macOS/Windows machines are the single most useful contribution right now. See [Help wanted](#help-wanted-macos--windows-verification) for the specific things to try.\n\n## How it differs from `chrome-devtools-mcp`\n\nGoogle's [`chrome-devtools-mcp`](https://github.com/ChromeDevTools/chrome-devtools-mcp) gives an AI Chrome DevTools Protocol access (DOM, console, network, screenshots). That covers a lot.\n\n`pwa-debug-layer` is **complementary** — it targets the things CDP can't reach:\n\n- **Your real, logged-in browser.** `chrome-devtools-mcp` spawns a fresh automated Chrome — which shows \"controlled by automated test software,\" blocks extension loading, and has none of your auth/session state, so authenticated apps are hard to debug ([the extensions/auth gap](https://medium.com/@vsanse24/no-extensions-allowed-the-chrome-devtools-mcp-dilemma-d58204aaab1f), [#265](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/265)). pwa-debug-layer's default `existing` mode attaches to **your normal profile**, with its real cookies, extensions, and service-worker state — exactly the real-profile SW/extension visibility people keep asking Google for ([#1173](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1173), [#96](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/96)).\n- **Framework state.** React fiber trees, Vue reactive state, Svelte component graphs, Solid signals — read via the framework's own devtools hooks (`__REACT_DEVTOOLS_GLOBAL_HOOK__`, `__vue_app__`, `_vnode`, etc.). CDP can't see these.\n- **Store state.** Redux / Zustand / Pinia / Jotai — read, subscribe, and dispatch.\n- **Service-worker, cache & installability state.** SW lifecycle + versions, CacheStorage contents + age, manifest installability gaps, IndexedDB/web storage, and an update-propagation / version-skew analyzer — the highest-volume PWA pain (stale cache, \"SW won't update,\" install failures, \"why are some users on old code\") that DevTools makes you piece together by hand.\n- **Shadow DOM, iframes, dynamically-injected library widgets.** WalletConnect modals, third-party SDK popups, and other widgets that escape standard DOM tooling.\n- **Page-world reach in general.** A MAIN-world script we inject reaches things isolated-world content scripts can't, and reaches them earlier than `initScript`-on-next-nav.\n- **Persistent ring buffers + rrweb-style replay** across navigations and reloads.\n- **Configurable filters** so the AI receives only the slice it asked for — no full-DOM noise.\n\nThe two are designed to coexist: install both, the AI uses each for what it does best, with zero tool-surface duplication.\n\n## Architecture\n\n```\n┌──────────────────┐  MCP (stdio)  ┌──────────────────────────┐\n│  Claude Code     │ ◄───────────► │  Native Messaging Host   │\n│  (or any MCP     │               │  - MCP server            │\n│   client)        │               │  - Ring buffers          │\n└──────────────────┘               │  - Replay/snapshot store │\n                                   └────────────┬─────────────┘\n                                                │ Native Messaging\n                                                │ (JSON over stdio)\n                                                ▼\n                                   ┌──────────────────────────┐\n                                   │  Extension Service Worker│\n                                   │  - chrome.debugger (CDP) │\n                                   │  - Tab/router            │\n                                   └────────────┬─────────────┘\n                                                │\n                       ┌────────────────────────┼────────────────────────┐\n                       ▼                        ▼                        ▼\n            ┌────────────────────┐  ┌────────────────────┐  ┌────────────────────┐\n            │ Content Script     │  │ Page-World Script  │  │ DevTools Panel     │\n            │ (isolated world)   │  │ (MAIN world)       │  │ (planned)          │\n            │ - DOM observe      │  │ - React/Vue hooks  │  │ - Human inspector  │\n            │ - Action exec      │  │ - fetch/XHR patch  │  │   of AI session    │\n            │ - Bridge to SW     │  │ - Bus/RxJS taps    │  │                    │\n            └────────────────────┘  └────────────────────┘  └────────────────────┘\n                       └────────── live page (the PWA being debugged) ─────────┘\n```\n\nThree components, one installable unit:\n\n- **Extension** owns the page (DOM, content scripts, page-world hooks).\n- **Native host** owns persistence and the MCP server (long-lived, can hold buffers, can write files).\n- **MCP** owns the AI contract.\n\nEach does what only it can. See [`docs/PLAN.md`](docs/PLAN.md) for the full design.\n\n## Browser support\n\n**Chromium-family only**, sideloaded. Tested against:\n\n- Chromium (native package)\n- Google Chrome (`.deb` / `.rpm`)\n- Brave Browser\n- Microsoft Edge (Linux `.deb`)\n- Vivaldi\n- Opera\n\nmacOS Application Support paths and Windows HKCU-registry registration are implemented and have unit-test coverage; the manual round-trip retest currently runs on Linux.\n\n### Snap browsers are not supported\n\nIf you installed your browser via **snap** (e.g. `snap install chromium` on Ubuntu), it will not work with `pwa-debug-layer`.\n\n**Why:** snap's `home` interface allows the browser to *read* files in `$HOME` but blocks *exec* of any binary whose resolved path crosses a hidden directory (`~/.nvm/...`, `~/.config/...`). The native messaging host launcher and the node binary it invokes both live under hidden paths in a normal install, so spawn fails with `Permission denied` and the service worker reports `Native host has exited.` There is no fix on the extension/host side that doesn't require copying ~125 MB of node into a non-hidden install dir per registration; not worth the install bloat for a setup most distros let you avoid.\n\n**What to do:** install your Chromium-family browser from a native package source instead:\n\n- **Ubuntu/Debian:** `apt install chromium` from the universe repo if you've enabled the non-snap source, or `apt install brave-browser` / `microsoft-edge-stable` from their respective `.deb` repos. The Chromium tarball from chromium.org also works.\n- **Fedora:** `dnf install chromium` is non-snap by default.\n- **Arch:** `pacman -S chromium`.\n\nFlatpak browsers are detected and have a manifest written, but flatpak confinement may also block exec — if it fails, run `flatpak override --user --filesystem=host <app-id>` and retry.\n\n## Installation\n\n### Prerequisites\n\n- Node.js ≥ 20.19 (developed on 23.x)\n- pnpm\n- A Chromium-family browser **not installed via snap** (see above)\n- An MCP-aware client (e.g. [Claude Code](https://docs.claude.com/en/docs/claude-code))\n\n### 1. Build the host and extension\n\n```sh\ngit clone https://github.com/<your-fork>/pwa-debug-layer\ncd pwa-debug-layer\npnpm install\npnpm build      # builds packages/host/dist/main.js and packages/extension/dist/\npnpm test       # full workspace unit suite (shared + host + extension)\n```\n\n### 2. Add the host to your MCP client\n\nFor Claude Code, add to your `.mcp.json` (project-scoped) or `~/.claude/mcp.json` (global):\n\n```json\n{\n  \"mcpServers\": {\n    \"pwa-debug\": {\n      \"type\": \"stdio\",\n      \"command\": \"node\",\n      \"args\": [\"/absolute/path/to/pwa-debug-layer/packages/host/dist/main.js\"]\n    }\n  }\n}\n```\n\nRestart Claude Code so it picks up the server.\n\n### 3. Load the extension\n\n1. Open `chrome://extensions` (or `brave://extensions`, etc.) in your browser.\n2. Toggle **Developer mode** on.\n3. Click **Load unpacked** and select `packages/extension/dist/`.\n4. Note the extension's ID (shown on the card). Or, ask Claude to discover it for you in the next step — the extension service worker logs `[pwa-debug/sw] id=<id>` on every boot.\n\n### 4. Tell Claude to set it up\n\nThe host registration is **AI-driven**. Six MCP tools are exposed for setup; Claude calls them via a guided flow:\n\n> **Set up pwa-debug. The extension ID is `<your-id>`** *(or omit the ID and Claude can fetch it from the SW console if you also have `chrome-devtools-mcp` installed.)*\n\nClaude will:\n\n1. Call `host_status` to see what's already registered.\n2. Call `host_register_extension(id)` — this writes a per-browser native-messaging manifest into every detected install (Chromium-family native, macOS Application Support, Windows HKCU registry), and drops a launcher script with an absolute node path (so the host spawns under sandboxed PATH environments).\n3. Tell you to reload the extension at `chrome://extensions`.\n4. After reload, call `host_status` again to confirm the manifest is on disk and the SW is connecting.\n\nWhen the round-trip works you'll see in the SW console (`Inspect views: service worker` on the extension card):\n\n```\n[pwa-debug/sw] connected to host\n[pwa-debug/sw] pong …\n[pwa-debug/sw] hello …          (5s after connect — host-pushed message proving bidirectional flow)\n```\n\n## Launching a browser + `chrome-devtools-mcp` coexistence\n\n`pwa-debug` and `chrome-devtools-mcp` are **two separate MCP servers** that share one browser. `pwa-debug` launches (or attaches to) a Chromium browser with a live remote-debugging port; `chrome-devtools-mcp` attaches to that same port over CDP. No proxying, no version coupling.\n\nRegister both with your client. For Claude Code:\n\n```sh\n# pwa-debug (this project) — adjust the path to your checkout\nclaude mcp add pwa-debug --scope user -- node /absolute/path/to/pwa-debug-layer/packages/host/dist/main.js\n\n# chrome-devtools-mcp (optional but recommended) — runs via npx, no global install\n# The --browserUrl port MUST match the port pwa-debug launches on (launch.defaultPort, default 9222).\nclaude mcp add chrome-devtools --scope user -- npx -y chrome-devtools-mcp@latest --browserUrl http://127.0.0.1:9222\n```\n\n### Or install `pwa-debug` as a Claude Code plugin\n\nThe repo ships a `.claude-plugin/` manifest whose MCP server runs the **published npm package** (`npx -y @aryanduntley/pwa-debug@latest`), so the plugin installs with **no clone and no build** — and picks up updates with **`/reload-plugins`**, no full restart:\n\n```sh\n# Add the marketplace straight from GitHub (no checkout needed)\n/plugin marketplace add aryanduntley/pwa-debug-layer\n# Install + enable the pwa-debug plugin\n/plugin install pwa-debug@pwa-debug\n# Bring the MCP server up with no restart:\n/reload-plugins\n```\n\n> **No build needed.** The plugin's MCP entry is `npx -y @aryanduntley/pwa-debug@latest`, which fetches the prebuilt host (and its bundled extension) from npm. *Working on a local clone instead?* Skip the plugin and register your own build directly: `claude mcp add pwa-debug --scope user -- node /absolute/path/to/pwa-debug-layer/packages/host/dist/main.js`.\n>\n> **`chrome-devtools-mcp` is still separate.** The plugin declares **only** the `pwa-debug` host — `chrome-devtools-mcp` stays the optional `claude mcp add chrome-devtools …` above (no version coupling, no owning its launch). The bundled **`chrome-devtools-coexistence`** skill walks you through registering it; with a plugin install, its \"make the tools appear\" step is `/reload-plugins`.\n\n> **Port must match.** `chrome-devtools-mcp`'s `--browserUrl` has to point at the exact port `pwa-debug` opens — the `launch.defaultPort` setting (default `9222`), or the active port from a current `pdl_launch_browser`. Rather than hand-write this, let Claude call **`pdl_register_chrome_devtools`**, which runs the `claude mcp add` above for you pinned to the right port (and **`pdl_check_setup`** flags a registration that is *unpinned* — no `--browserUrl`, so it spawns its own isolated browser — or pointed at the wrong port).\n\n> **Mind the (re)connect.** A registration added or changed via `claude mcp add` only takes effect when the server (re)connects. Fastest path: open **`/mcp`** and **reconnect** `chrome-devtools` — this reloads it from the current registration (verified to pick up a changed `--browserUrl`) with **no full restart and no context loss**. If the client won't load it that way, **fully restart** Claude Code as a fallback. If instead you install `chrome-devtools-mcp` as a **plugin**, run **`/reload-plugins`**. The bundled **`chrome-devtools-coexistence`** skill walks Claude through all paths and, for the full-restart fallback, hands you a context note to paste back afterward.\n\n> The host has no `install`/`serve` subcommand — `dist/main.js` auto-detects its mode: launched by Chrome (argv starts with `chrome-extension://`) it runs as the native-messaging host; launched by your MCP client it runs as the MCP server.\n\nThen let Claude drive setup and launch:\n\n1. **`pdl_check_setup`** — reports `{ ok, gaps[], recommendations[] }`: whether `chrome-devtools-mcp` is **registered** (read from the `claude` CLI) **and pinned with a `--browserUrl`** at the right debug port — it flags both an *unpinned* registration (which would spawn its own isolated browser instead of attaching to yours) and one pointed at the wrong port; the host manifest is installed (including **per active sandbox profile**); the extension dist is present; and an extension ID is registered. Follow its `next_steps` to close any gap.\n2. **`pdl_register_chrome_devtools`** — writes the `chrome-devtools-mcp` registration for you, pinned to the active/`launch.defaultPort` port (idempotent; re-points it if it's unpinned or on the wrong port). *(Mutates your MCP config — Claude will ask first; a restart/reconnect follows, see above.)*\n3. **`pdl_install_extension`** — copies the extension to `~/Downloads/pwa-debug-extension` (or a `target` you pass) with `chrome://extensions` \"Load unpacked\" instructions. *(On most browsers a sandbox mode preloads the extension so you can skip this; on **branded Google Chrome 142+** preload is impossible — this is the manual install the sandbox launch will point you to.)*\n4. **`pdl_launch_browser`** — launches/attaches a browser with the debug port live and returns the `browserUrl` to hand to `chrome-devtools-mcp`. **Always launch first:** call this *before* any `chrome-devtools-mcp` tool, so the browser + debug port exist for it to attach to. Follow its `next_steps` — they carry the brand/version-specific guidance (preloaded vs. manual *Load unpacked*, or the modern-Chromium port caveat).\n5. **`pdl_browser_status`** — shows what's been launched (browser, profile mode, port, pid), re-probes each debug port for liveness, and reports the extension service-worker heartbeat.\n\n**The validated one-profile recipe** (what these tools converge on, verified live on Brave 148):\n\n```\npdl_launch_browser({ browser: \"brave\", mode: \"sandbox-persistent\" })\n  → dedicated profile at ~/.pwa-debug/profiles/brave with:\n      • the native-messaging manifest auto-written into the profile (so the extension connects), and\n      • the pwa-debug extension preloaded via --load-extension (Brave honors it), and\n      • --remote-debugging-port live (custom profile dir, so Chromium 136+ opens it).\npdl_register_chrome_devtools()  → pins chrome-devtools-mcp at that port\nrestart / reconnect chrome-devtools-mcp  → it attaches to the SAME browser\n```\n\nNow `chrome-devtools-mcp` (CDP) and pwa-debug (extension → page-world) drive the same tab simultaneously — independent, non-contending channels (the extension uses page-world injection, **not** `chrome.debugger`, so there's no CDP-attacher contention).\n\n## Profile modes\n\n`pdl_launch_browser` takes `mode` (default `existing`), `browser` (defaults to your system-default Chromium browser), and `port` (default `9222`).\n\n| Mode | Profile | When to use |\n|---|---|---|\n| **`existing`** *(default)* | Your normal browser profile | Debugging your real browsing session **with pwa-debug** (your real cookies, extensions, SW/cache state). Degrades gracefully: **(a)** debug port already live → attaches; **(b)** browser running *without* a debug port → opens a new window in the existing session (your extension tools work, `chrome-devtools-mcp` does not); **(c)** browser not running → spawns fresh with your profile. **Modern-Chromium caveat (136+):** Chromium refuses `--remote-debugging-port` on the **default** profile, so on a current browser `existing` mode gives you pwa-debug but **not** a CDP port — the launch reports this (`attached:false`, no false `browserUrl`) and steers you to `sandbox-persistent` for `chrome-devtools-mcp`. |\n| **`sandbox-persistent`** | `~/.pwa-debug/profiles/<browser>/` (persists across restarts) | The **canonical both-tools profile** — a dedicated profile beside your main browser, with its own `--user-data-dir` (so the debug port works on Chromium 136+). The launcher auto-writes the native-messaging manifest into the profile and, on most browsers, **preloads** the pwa-debug extension via `--load-extension`. **Branded-Google-Chrome 142+ caveat:** Chrome removed `--load-extension`, so there the launch brings up the **debug port** (for `chrome-devtools-mcp`) but **not** the extension — it returns a one-time manual *Load unpacked* walkthrough (persists in this profile), or steers you to a non-Google Chromium where preload just works. |\n| **`sandbox-temp`** | a fresh `mktemp` dir (removed on host shutdown) | One-off / CI / clean-state runs. Same as `sandbox-persistent`, but throwaway — so the Chrome-142+ manual-load step won't persist usefully here; prefer a non-Google Chromium for temp runs that need the extension. |\n\n**Extension preload depends on the browser brand + version** (the launcher detects it from `--version` and adapts):\n\n| Browser | `--load-extension` (sandbox preload) |\n|---|---|\n| Brave, Chromium, Edge, Opera, Vivaldi, Chrome for Testing | ✅ works on current versions |\n| Google Chrome ≤ 136 | ✅ works |\n| Google Chrome 137–141 | ✅ works — launcher adds `--disable-features=DisableLoadExtensionCommandLineSwitch` |\n| **Google Chrome ≥ 142** | ❌ removed — launcher omits the dead flag and guides a one-time manual *Load unpacked* (or use Brave/Chromium) |\n\n**Recommended coexistence setup:** `sandbox-persistent` on a **non-Google Chromium** (e.g. Brave or Chromium). That single profile gets the extension preloaded **and** a live debug port, so pwa-debug and `chrome-devtools-mcp` drive the same browser with zero manual steps.\n\n## Browser support matrix\n\nChromium-family only (Firefox doesn't speak CDP). Linux is first-class. macOS/Windows binary detection, profile/user-data-dir paths, **and system-default detection** (macOS LaunchServices, Windows `UserChoice` registry) are all implemented with unit coverage, but **live verification on real macOS/Windows machines is still needed — see [Help wanted](#help-wanted-macos--windows-verification).**\n\n| Browser | PATH names probed | Standard Linux binary | Linux profile dir (`existing` mode) |\n|---|---|---|---|\n| Chrome | `google-chrome`, `google-chrome-stable` | `/opt/google/chrome/chrome`, `/usr/bin/google-chrome*` | `~/.config/google-chrome` |\n| Chromium | `chromium`, `chromium-browser` | `/usr/bin/chromium*`, `/snap/bin/chromium` | `~/.config/chromium` |\n| Edge | `microsoft-edge`, `microsoft-edge-stable` | `/opt/microsoft/msedge/msedge` | `~/.config/microsoft-edge` |\n| Brave | `brave-browser`, `brave` | `/opt/brave.com/brave/brave-browser` | `~/.config/BraveSoftware/Brave-Browser` |\n| Vivaldi | `vivaldi`, `vivaldi-stable` | `/opt/vivaldi/vivaldi` | `~/.config/vivaldi` |\n| Opera | `opera` | `/usr/bin/opera`, `/opt/opera/opera` | `~/.config/opera` |\n\n- **System default:** the launcher prefers your system-default browser when you don't pass one — Linux via `xdg-settings get default-web-browser`, macOS via LaunchServices (`defaults read … LSHandlers`), Windows via the HKCU `UrlAssociations\\http\\UserChoice` ProgId. The macOS/Windows paths are implemented + unit-tested but not yet exercised on a real machine.\n- **Default debug port** is `9222` (the `chrome-devtools-mcp` convention); override it without passing `port` each time via the `launch.defaultPort` setting (`settings.set`).\n- **Snap profiles:** when launching a snap-packaged browser (`/snap/bin/…`) in `existing` mode, the launcher now resolves its confined profile (`~/snap/<snap>/common/<cfg>`) instead of `~/.config` — but note snap browsers still can't run the native-messaging host (see snap section), so this only matters if/when that confinement is lifted.\n- **Brave Shields** can block the content script on a site — set Shields **Down** for the site if `session_ping` reports `page_blocks_scripts`.\n- **Snap browsers are unsupported** for the native-messaging host (see below); the launcher can still spawn them, but the host round-trip won't connect. Use a native-package browser.\n- **Flatpak** installs get a manifest written, but confinement may block exec — run `flatpak override --user --filesystem=host <app-id>` and retry.\n\n## Help wanted: macOS / Windows verification\n\nDevelopment happens on Linux, so the macOS and Windows code paths are **written and unit-tested with injected fakes, but never run on a real machine.** If you're on macOS or Windows, trying these and reporting back (open an issue with the output) is the single most useful contribution right now:\n\n**macOS**\n- `pdl_check_setup` / `pdl_install_extension` — does the extension resolve and copy, and do the `chrome://extensions` instructions work?\n- Browser binary detection under `/Applications/*.app/Contents/MacOS/…`.\n- System-default detection: `defaults read com.apple.LaunchServices/com.apple.launchservices.secure LSHandlers` — does the parser pick the right browser? (Paste the raw output if it doesn't.)\n- `pdl_launch_browser` mode `existing` (profile under `~/Library/Application Support/…`) and both sandbox modes.\n\n**Windows**\n- Browser detection under `%PROGRAMFILES%` / `%LOCALAPPDATA%`.\n- System-default detection: `reg query \"HKCU\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice\" /v ProgId` — does the ProgId map to the right browser?\n- `pdl_launch_browser` (profile under `%LOCALAPPDATA%\\…\\User Data`) and the HKCU native-messaging registration.\n\n**Any OS**\n- `pdl_browser_status` after restarting the host — launches now persist to `launches.json`, so previously-launched browsers should still be listed (with a fresh liveness re-probe). Confirm the list survives a restart and that closed browsers show as not-live.\n\nThe launcher never kills your running browser and sandbox modes use throwaway/dedicated profiles, so this is low-risk to try.\n\n## Troubleshooting\n\n### Verification sequence\n\nWhen something isn't working, walk this ladder — each step localizes the failure:\n\n1. **`pdl_check_setup`** — are all setup gaps closed? (CDP reachable, manifest installed, extension present, ID registered.)\n2. **`pdl_browser_status`** — is a browser launched and is its debug port still live? Is the extension service worker connected (recent heartbeat)?\n3. **`host_status`** — is the native-messaging host registered and is an NMH instance connected?\n4. **`session_ping`** — does a full MCP → SW → page-world round-trip succeed on the active tab? (See the typed `pageWorldError` table below.)\n\n### Common launcher gotchas\n\n- **\"I launched in `existing` mode but `chrome-devtools-mcp` can't attach.\"** Two causes. **(1)** Chrome opens `--remote-debugging-port` only at process start — if the browser was already running without it, `pdl_launch_browser` opens a new window (sub-state **b**) but can't add the port to the live process (`attached:false`). **(2)** On **Chromium 136+**, the port is refused on the *default* profile entirely, even on a fresh start. Either way: use `mode: sandbox-persistent` (a dedicated profile where the port works) for `chrome-devtools-mcp`.\n- **Brave/Chrome says \"Opening in existing browser session.\"** That's sub-state **b** — the binary handed your request to the already-running process instead of starting a fresh one with the port. Same fix as above.\n- **Sandbox launch came up but pwa-debug never connects (Google Chrome 142+).** Branded Google Chrome 142+ permanently ignores `--load-extension`, so the sandbox can't auto-preload the extension — the launch says so and gives you a one-time `chrome://extensions` → Developer mode → *Load unpacked* walkthrough (it persists in that dedicated profile). Or relaunch with `browser: \"brave\"` / `\"chromium\"`, where preload works automatically. (`chrome-devtools-mcp` still works either way — the debug port is live.)\n- **`chrome-devtools-mcp` opened its own blank browser instead of using yours.** Its registration is **unpinned** (no `--browserUrl`). Run `pdl_register_chrome_devtools` (or re-add with `--browserUrl http://127.0.0.1:<port>`) and restart/reconnect it; `pdl_check_setup` flags this.\n- **Added the MCP server but the tools don't appear.** `.mcp.json` / client MCP config is read at startup — restart your MCP client (or `/mcp`-reconnect that server) after adding `pwa-debug` or `chrome-devtools`.\n- **Tools worked, then stopped after I reloaded the extension.** Reloading the extension at `chrome://extensions` detaches content scripts from already-open tabs. Hard-refresh the page tab (Ctrl+Shift+R); the SW also auto-reinjects on the next `session_ping` (look for `pageWorldSelfHealed: true`). Sandbox modes avoid this entirely (extension preloaded before tabs open).\n\n\n### `session_ping` returns `pageWorld: null` with a typed `pageWorldError`\n\n`session_ping` reports failure modes as **typed codes** in `pageWorldError` (machine-readable) plus the original chrome-runtime string in `pageWorldErrorMessage` (for logs). The MCP `next_steps[]` field carries imperative, code-specific guidance — AI clients should relay it verbatim. Tabs that simply predated the extension reload (the most common dev-loop friction) are auto-recovered by the SW via `chrome.scripting.executeScript`; when that succeeds, `pageWorld` is populated and `pageWorldSelfHealed: true` appears alongside it. The table below is the canonical mapping (single source of truth: `NEXT_STEPS_BY_CODE` in `packages/host/src/mcp/tools/session_ping.ts`).\n\n| `pageWorldError` | What it means | What to do |\n|---|---|---|\n| *(absent)* with `pageWorldSelfHealed: true` | The static content script was missing on the active tab; the SW silently re-injected `content-script.js` + `page-world.js` and retried. No action needed. | Informational only. |\n| `cs_not_attached_refresh_tab` | Auto-recovery was attempted but did not stick (page rejected the injection or reloaded mid-flight). | Hard-refresh the page tab (Ctrl+Shift+R) and retry. If it repeats, reload the extension at `chrome://extensions` then hard-refresh. |\n| `page_blocks_scripts` | A content blocker is rejecting the script (Brave Shields, uBlock Origin, AdGuard, or similar). Site CSP is also possible. | **Brave:** click the lion icon → set Shields **Down** for the site → refresh → retry. **uBlock Origin / similar:** disable for this site → refresh → retry. If neither, the site's own CSP is blocking and pwa-debug cannot bypass it. |\n| `page_world_blocked` | The content script attached but the MAIN-world page-world bridge cannot be reached — the site's Content-Security-Policy blocks the inline script tag. | Site-level restriction; cannot bypass. Console + network capture may still work via the content-script side, but live page-world reads (state, evaluate) will not. |\n| `restricted_url` | The active tab is on a URL browsers do not allow extensions to touch (`chrome://`, `chromewebstore.google.com`, `about:`, `devtools://`, `file://`, `view-source:`, etc.). | Switch focus to a regular `http(s)` tab of the PWA, then retry. |\n| `no_active_tab` | No active `http(s)` tab is focused (DevTools window or extension popup may be focused instead). | Focus a regular browser tab and retry. |\n| `cs_inject_failed` | The auto-recovery `chrome.scripting.executeScript` itself failed. The extension cannot reach this tab. | Reload the extension at `chrome://extensions` and hard-refresh the page (Ctrl+Shift+R). If it persists, the URL may be one the browser blocks all extensions from — check the address bar. |\n\nTo confirm the content script attached after a successful round-trip, open the page tab's DevTools (F12 on the page itself, **not** the SW console) and look for `[pwa-debug/cs] attached at <url>` in the Console.\n\n## MCP tool surface\n\nEvery tool returns a structured response of the form `{ ok, data, error?, next_steps[] }`. The `next_steps` array encodes the rules of engagement for the AI — what to call next based on the actual response shape — mirroring the AIMFP `return_statements` pattern. The canonical list lives in `packages/host/src/mcp/tools/index.ts`.\n\n### Host management & setup\n\n| Tool | Purpose |\n|---|---|\n| `host_status` | Install/liveness state: registered IDs, manifest paths, launcher path, active connections. Cheap, idempotent. **Always call first.** |\n| `host_register_extension(id)` / `host_unregister_extension(id)` | Add/remove an extension ID across per-browser manifests + launcher script. Idempotent. |\n| `host_list_registrations` / `host_reset` | Read registered IDs; destructive cleanup to re-bootstrap. |\n| `session_ping` | Full MCP → IPC → NMH → SW → page-world round-trip with typed `pageWorldError` codes + self-heal. |\n| `pdl_check_setup` | Diagnose setup → `{ ok, gaps[], recommendations[] }` (CDP reachable, manifest installed, extension present, ID registered). |\n| `pdl_install_extension({ target? })` | Copy the extension to a folder for unpacked install. |\n\n### Browser launcher\n\n| Tool | Purpose |\n|---|---|\n| `pdl_launch_browser({ browser?, port?, mode? })` | Launch/attach a Chromium browser with a live debug port. `mode`: `existing` (default), `sandbox-persistent`, `sandbox-temp`. Returns `browserUrl` for `chrome-devtools-mcp`. |\n| `pdl_browser_status` | Managed launches (browser, profile mode, port, pid) with live debug-port re-probe + extension SW heartbeat. |\n\n### Capture, evaluate, replay\n\n| Tool | Purpose |\n|---|---|\n| `console_tail` / `network_tail` / `error_tail` | Cursor-paginated, filterable tails of the persistent capture ring buffers (memory + disk spill). |\n| `recent_events` | Recent captured events across kinds for quick verification. |\n| `evaluate` | Evaluate an expression in the page world. |\n| `session_record` / `session_replay` | rrweb session recording + cursor-paginated replay. |\n| `source_map_resolve` | Resolve generated stack frames to original `src/…:line:col`. |\n| `settings_list_schema` / `settings_get` / `settings_set` | Read the typed settings schema; get/set values (allowlist, capture filters, disk-spill, etc.). |\n\n### PWA runtime diagnostics\n\nThe namesake suite — read the debugged PWA's service-worker, cache, installability, storage, and update state. All page-world reads against your **real profile**; CDP / `chrome-devtools-mcp` can't surface these.\n\n| Tool | Purpose |\n|---|---|\n| `sw_status` | Service-worker registrations: installing/waiting/active versions, `updateViaCache`, controller, and whether an update is stuck **waiting** (the #1 \"why isn't my update showing\"). |\n| `sw_lifecycle_tail` | Cursor-paginated stream of SW lifecycle events (updatefound / statechange / controllerchange). |\n| `cache_list` / `cache_inspect` / `cache_match` | CacheStorage caches + per-entry `{ url, status, content-type, size, ageSeconds, cache-control }`, and which cache serves a given URL — the core of stale-cache debugging. |\n| `pwa_status` | Display mode / standalone, controller, permissions, and a live capability matrix (Push / Background Sync / Periodic Sync / Badging / File System Access / Window Controls Overlay). |\n| `pwa_installability` | Discover + parse the manifest and run installability checks → structured `{ supported, gaps[], fixes[] }` with per-gap remediation. |\n| `storage_get` | localStorage / sessionStorage snapshot (capped). |\n| `idb_list` / `idb_query` | IndexedDB databases + object-store schema, then a read-only capped slice of records — *inspect IndexedDB live*. |\n| `pwa_update_analyze` | Correlates SW status + cache ages + recent 404s into a verdict: waiting-update-on-active-client, cached-HTML-older-than-JS version skew, chunk 404s — *why are some users on old code?* |\n| `pwa_snapshot` | One capped runtime-state blob (SW + store + web storage + IndexedDB + cache names) for deterministic bug-repro / hand-off. |\n\n### Framework introspection\n\nRead via each framework's own model — things CDP can't see. Tree/state where the framework persists it; element-level find everywhere.\n\n| Tool family | Purpose |\n|---|---|\n| `react_tree` / `react_get_state` / `react_find_by_text` / `react_find_by_role` | React fiber-tree introspection + props/state/hooks by stable id + component lookup. |\n| `vue_tree` / `vue_get_state` / `vue_find_by_text` / `vue_find_by_role` | Vue 3 `ComponentInternalInstance` tree + reactive state + lookup (parity with React). |\n| `svelte_components` / `svelte_find_by_text` / `svelte_find_by_role` | Svelte component discovery + `__svelte_meta` source locations + element lookup (no instance state — Svelte exposes none). |\n| `solid_detect` / `solid_find_by_text` / `solid_find_by_role` | Solid detection + element-level find (no persisted tree/state without `@solid-devtools`). |\n\n### Store introspection\n\n| Tool family | Purpose |\n|---|---|\n| `redux_get_state` / `redux_subscribe` / `redux_tail` / `redux_dispatch` | Redux read, change-delta subscribe/tail, JSONPath-lite slice, (opt-in) dispatch. |\n| `store_get_state` / `store_subscribe` / `store_tail` / `store_dispatch` | Framework-agnostic adapter covering **Zustand / Pinia / Jotai** through one contract (read / subscribe / dispatch by name). |\n\n### Interaction & touch gestures\n\nFramework-agnostic native-event sequences dispatched so React/Vue/etc. delegated synthetic-event systems fire. Each targets a unified locator (selector / role / text / framework stable-id).\n\n| Tool | Purpose |\n|---|---|\n| `pdl_click` / `pdl_dblclick` / `pdl_hover` | Pointer interactions. |\n| `pdl_fill` / `pdl_submit` / `pdl_select_option` / `pdl_uncheck` | Form interactions. |\n| `pdl_focus` / `pdl_blur` / `pdl_key_press` / `pdl_type_sequence` | Focus + keyboard. |\n| `pdl_drag` / `pdl_scroll` / `pdl_swipe` / `pdl_tap` / `pdl_double_tap` / `pdl_long_press` / `pdl_pinch` | Touch/gesture primitives (direction/distance/duration/steps model — no hand-built coordinates). |\n\n### Library popups\n\n| Tool | Purpose |\n|---|---|\n| `popup_tail` | Tail the library-popup event stream (WalletConnect / RainbowKit / SDK modals, incl. shadow-root + nested). |\n| `popup_record` / `popup_replay` | Intent-driven recording of a popup's full primary+nested stream (immune to ring-buffer eviction) + flat/primary/tree replay. |\n| `popup_failures` | Correlate a popup's auth/connect failure with the console errors + failed requests during its open window. |\n\n## Roadmap\n\n- **Foundation** ✅ — pnpm workspace + build pipeline; MV3 extension loads cleanly; native-messaging round-trip; AI-managed host registration; cross-platform install (Linux native + macOS + Windows registry; snap unsupported); MCP↔IPC↔NMH↔SW bridge.\n- **Capture** ✅ — console / network (fetch/XHR/WebSocket) / DOM-mutation / lifecycle producers; host ring buffers with disk spill + archive pruning; filterable, cursor-paginated `console_tail` / `network_tail`.\n- **Framework introspection** ✅ — **React, Vue, Svelte, Solid** (trees/state where the framework persists them, find-by-text/role everywhere); page-world `evaluate`.\n- **Store introspection** ✅ — **Redux** (read/subscribe/tail/dispatch) plus a framework-agnostic adapter covering **Zustand, Pinia, Jotai**.\n- **Interaction & gestures** ✅ — discrete actions (click/fill/submit/hover/focus/blur/select/key/type) + touch gestures (drag/scroll/swipe/tap/double-tap/long-press/pinch) over a unified locator.\n- **Library popups** ✅ — `popup_record` / `popup_replay` / `popup_tail` / `popup_failures` for WalletConnect & SDK modals (incl. shadow-root + nested).\n- **Replay & source maps** ✅ — rrweb `session_record` / `session_replay`; `source_map_resolve` for stack frames.\n- **Settings** ✅ — typed schema store (allowlist/blocklist, per-kind capture filters, per-site read controls, disk-spill).\n- **Browser launcher** ✅ — `pdl_launch_browser` (existing + sandbox-persistent + sandbox-temp), `pdl_check_setup`, `pdl_browser_status`, `pdl_close_browser`, `pdl_install_extension`, `pdl_register_chrome_devtools`, and `chrome-devtools-mcp` coexistence.\n- **PWA Runtime Diagnostics** ✅ — the namesake suite, live-verified against a real PWA. Service-worker introspection (`sw_status` + `sw_lifecycle_tail`: installing/waiting/active versions, `updateViaCache`, skipWaiting/claim/controllerchange); CacheStorage inspection (`cache_list` / `cache_inspect` / `cache_match` with size/age + match-by-URL); `pwa_status` (display mode, controller, permissions, live capability matrix — Push/Background Sync/Periodic Sync/Badging/File System Access/Window Controls Overlay); installability diagnostics (`pwa_installability` → structured `{gaps[], fixes[]}`); IndexedDB/web-storage inspection (`idb_list` / `idb_query` / `storage_get`); update-propagation / version-skew analysis (`pwa_update_analyze`); and a one-shot runtime-state snapshot (`pwa_snapshot`). Closes the stale-cache / \"SW won't update\" / install-failure / version-skew pain cluster against your real profile.\n- **Later** — DevTools panel for human observation of an AI session; multi-tab routing model; event-causality graph (click → action → request → SW → cache → re-render); production-safe diagnostic mode for real-user failure capture.\n- **Deferred** — Firefox port (needs WebDriver BiDi, not CDP); macOS/Windows live verification ([help wanted](#help-wanted-macos--windows-verification)); mobile; hosted/team mode.\n- **Intentionally not pursued** — **Chrome Web Store distribution.** The extension grants broad page access (DOM, framework state, stores, network) and is only meaningful alongside its MCP host. It ships **bundled with the MCP only** and is installed via a manual, dev-mode \"Load unpacked\" (`pdl_install_extension` hands you the path + steps) — so every user knows exactly what they're running and why. Disabling Chrome Developer mode auto-disables it.\n\n## Code style\n\n- FP-only: pure functions, immutability, no OOP, no classes-with-methods.\n- Side effects (CDP calls, file I/O, native messaging, MCP transport) at the edges; core logic pure.\n- OOP library interfaces (Chrome APIs, MCP SDK, `winreg`) wrapped in thin functional adapters with injection points for tests.\n\n## Repo layout\n\n```\npwa-debug-layer/\n├── packages/\n│   ├── host/                Native-messaging host + MCP server (Node, ESM, rollup-bundled)\n│   ├── extension/           WebExtension (MV3) — service worker, content script, page-world\n│   └── shared/              Cross-package types\n├── docs/\n│   ├── PLAN.md              Full design doc (architecture, capability matrix, phased plan)\n│   └── goals.txt\n├── examples/                (future: test PWAs)\n└── reference/               Read-only reference checkouts\n```\n\n## Contributing\n\nThis is a personal project under active redesign. PRs welcome but please open an issue first to discuss scope — the architecture is still settling. The FP / no-OOP discipline applies to all contributions; see `CLAUDE.md` for the full rules.\n\n## License\n\nMIT — see [`LICENSE`](LICENSE).\n",
  "bytes": 43157,
  "sha": "1676b3a0bfc8603543667c5bb1dcde736d41e2bbc029bc5cdc66337aab9eab10",
  "repo_slug": "aryanduntley/pwa-debug-layer",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_aryanduntley_pwa_debug_layer_10c19369/readme"
}