@scayle/storefront-devtools 0.2.0-alpha.1 → 0.2.0-alpha.2

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 (106) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +113 -83
  3. package/dist/_chunks/coalesce-cmmfn4qq.mjs +1585 -0
  4. package/dist/group.d.ts +29 -21
  5. package/dist/group.d.ts.map +1 -1
  6. package/dist/index.d.ts +4 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.mjs +40 -65
  9. package/dist/inertia/channel.d.ts +54 -0
  10. package/dist/inertia/channel.d.ts.map +1 -0
  11. package/dist/inertia/client/index.d.ts +1 -6
  12. package/dist/inertia/client/index.d.ts.map +1 -1
  13. package/dist/inertia/client/index.mjs +246 -109
  14. package/dist/inertia/client/publish.d.ts +24 -0
  15. package/dist/inertia/client/publish.d.ts.map +1 -0
  16. package/dist/inertia/core/network.d.ts +1 -1
  17. package/dist/inertia/core/network.d.ts.map +1 -1
  18. package/dist/inertia/core/response.d.ts +3 -3
  19. package/dist/inertia/core/response.d.ts.map +1 -1
  20. package/dist/inertia/plugin.d.ts +8 -6
  21. package/dist/inertia/plugin.d.ts.map +1 -1
  22. package/dist/orpc/channel.d.ts +29 -0
  23. package/dist/orpc/channel.d.ts.map +1 -0
  24. package/dist/orpc/client/index.d.ts +1 -6
  25. package/dist/orpc/client/index.d.ts.map +1 -1
  26. package/dist/orpc/client/index.mjs +152 -137
  27. package/dist/orpc/client/publish.d.ts +15 -0
  28. package/dist/orpc/client/publish.d.ts.map +1 -0
  29. package/dist/orpc/core/serialize.d.ts +1 -1
  30. package/dist/orpc/core/stats.d.ts +10 -0
  31. package/dist/orpc/core/stats.d.ts.map +1 -0
  32. package/dist/orpc/core/store.d.ts +1 -2
  33. package/dist/orpc/core/store.d.ts.map +1 -1
  34. package/dist/orpc/plugin.d.ts +8 -5
  35. package/dist/orpc/plugin.d.ts.map +1 -1
  36. package/dist/shared/clipboard.d.ts +2 -3
  37. package/dist/shared/clipboard.d.ts.map +1 -1
  38. package/dist/shared/coalesce.d.ts +14 -0
  39. package/dist/shared/coalesce.d.ts.map +1 -0
  40. package/dist/shared/panel-channel.d.ts +49 -0
  41. package/dist/shared/panel-channel.d.ts.map +1 -0
  42. package/dist/shared/publish-options.d.ts +13 -0
  43. package/dist/shared/publish-options.d.ts.map +1 -0
  44. package/dist/shared/safe-clone.d.ts +2 -1
  45. package/dist/shared/safe-clone.d.ts.map +1 -1
  46. package/dist/storefront/plugin.d.ts +6 -4
  47. package/dist/storefront/plugin.d.ts.map +1 -1
  48. package/dist/ui/assets/App-BlEKD1MK.js +4 -0
  49. package/dist/ui/assets/App-C1rr07m5.js +1 -0
  50. package/dist/ui/assets/App-C5-JscO0.css +1 -0
  51. package/dist/ui/assets/App-M0iU7c4p.css +1 -0
  52. package/dist/ui/assets/App-VElUrn9P.js +5 -0
  53. package/dist/ui/assets/App-jiQKInbo.css +1 -0
  54. package/dist/ui/assets/_plugin-vue_export-helper-BDNMzG2s.js +1 -0
  55. package/dist/ui/assets/channel-D7V7aU79.js +1 -0
  56. package/dist/ui/assets/clipboard-BnEeWyfh.css +1 -0
  57. package/dist/ui/assets/clipboard-n-T06WI3.js +1 -0
  58. package/dist/ui/assets/panel-channel-DjMENemf.js +2 -0
  59. package/dist/ui/assets/panels-B4LTsmli.css +1 -0
  60. package/dist/ui/assets/panels-BLV_p6Kz.js +2 -0
  61. package/dist/ui/assets/preload-helper-J6bGWMBq.js +2 -0
  62. package/dist/{vue-devtools/ui/assets/index-Dby9TIQH.js → ui/assets/vue-BAMMushd.js} +19 -20
  63. package/dist/{vue-devtools/ui/assets/index-sSkqWPzY.css → ui/assets/vue-Kq4tvBbL.css} +1 -1
  64. package/dist/ui/panels/index.html +15 -0
  65. package/dist/{vue-devtools/ui → ui/vue}/index.html +4 -2
  66. package/dist/vue-devtools/client/index.mjs +0 -1
  67. package/dist/vue-devtools/plugin.d.ts +10 -9
  68. package/dist/vue-devtools/plugin.d.ts.map +1 -1
  69. package/package.json +11 -29
  70. package/dist/_chunks/clipboard-DrVHc2QA.mjs +0 -61
  71. package/dist/_chunks/diff-DzJ5arJj.mjs +0 -169
  72. package/dist/_chunks/panel-uV5ITqhY.mjs +0 -51
  73. package/dist/_chunks/redact-DmIF-iyk.mjs +0 -96
  74. package/dist/_chunks/useStore-Db8RZW8T.mjs +0 -10
  75. package/dist/inertia/client/index.d.mts +0 -13
  76. package/dist/inertia/renderer/index.d.mts +0 -16
  77. package/dist/inertia/renderer/index.mjs +0 -771
  78. package/dist/orpc/client/index.d.mts +0 -13
  79. package/dist/orpc/renderer/index.d.mts +0 -16
  80. package/dist/orpc/renderer/index.mjs +0 -457
  81. package/dist/resource-center/client/index.mjs +0 -21
  82. package/dist/resource-center/client.d.ts +0 -3
  83. package/dist/resource-center/client.d.ts.map +0 -1
  84. package/dist/resource-center/plugin.d.ts +0 -12
  85. package/dist/resource-center/plugin.d.ts.map +0 -1
  86. package/dist/shared/use-window-store.d.ts +0 -18
  87. package/dist/shared/use-window-store.d.ts.map +0 -1
  88. package/dist/storefront/renderer/index.d.mts +0 -16
  89. package/dist/storefront/renderer/index.mjs +0 -124
  90. /package/dist/{vue-devtools/ui → ui}/assets/css-8QJvnqUH.js +0 -0
  91. /package/dist/{vue-devtools/ui → ui}/assets/css-CaJ9TzKE.js +0 -0
  92. /package/dist/{vue-devtools/ui → ui}/assets/diff-woXpYk--.js +0 -0
  93. /package/dist/{vue-devtools/ui → ui}/assets/html-BHDw6EPc.js +0 -0
  94. /package/dist/{vue-devtools/ui → ui}/assets/html-GueGcY0x.js +0 -0
  95. /package/dist/{vue-devtools/ui → ui}/assets/javascript-BzbQS41l.js +0 -0
  96. /package/dist/{vue-devtools/ui → ui}/assets/javascript-Yelw-5ZN.js +0 -0
  97. /package/dist/{vue-devtools/ui → ui}/assets/json-B88Oqtu5.js +0 -0
  98. /package/dist/{vue-devtools/ui → ui}/assets/json-CaljABEy.js +0 -0
  99. /package/dist/{vue-devtools/ui → ui}/assets/shellscript-CLZ0U2zV.js +0 -0
  100. /package/dist/{vue-devtools/ui → ui}/assets/typescript-Utq2Cl8c.js +0 -0
  101. /package/dist/{vue-devtools/ui → ui}/assets/typescript-j_1H8WHN.js +0 -0
  102. /package/dist/{vue-devtools/ui → ui}/assets/vitesse-dark-BZCL-v6S.js +0 -0
  103. /package/dist/{vue-devtools/ui → ui}/assets/vitesse-light-VbXTXTou.js +0 -0
  104. /package/dist/{vue-devtools/ui → ui}/assets/vue-B1Tf5CHw.js +0 -0
  105. /package/dist/{vue-devtools/ui → ui}/assets/vue-html-CGMs_6qn.js +0 -0
  106. /package/dist/{vue-devtools/ui → ui}/assets/yaml-rwi0_p6S.js +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @scayle/storefront-devtools
2
2
 
3
+ ## 0.2.0-alpha.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Added dependency `@vitejs/devtools-kit@catalog:`
8
+ - Removed dependency `sirv@catalog:`
9
+ - The Inertia.js, oRPC, and Storefront context panels now run as iframe apps in the dock. Page scripts in your app send them their data over `devframe/in-page-channel`. The panels keep rendering when you switch the dock between Float, Edge, and Popup, and they no longer show up as extra apps in Vue DevTools.
10
+
11
+ The Resource Center entry is gone, and so are the `./inertia/renderer`, `./orpc/renderer`, `./storefront/renderer`, and `./resource-center/client` subpath exports. The dock loads the panels itself, so nothing outside this package imported them.
12
+
3
13
  ## 0.2.0-alpha.1
4
14
 
5
15
  ### Patch Changes
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
  </h4>
11
11
 
12
12
  <p align="center">
13
- The SCAYLE <strong>Storefront DevTools</strong> package wires the Vite DevTools dock into the Storefront Application V3, with an Inertia.js inspector, an oRPC call inspector, a Storefront context viewer, a Resource Center, and Vue DevTools, behind a single Vite plugin call.
13
+ The SCAYLE <strong>Storefront DevTools</strong> package adds the Vite DevTools dock to the Storefront Application V3 with one Vite plugin call. The dock holds an Inertia.js inspector, an oRPC call inspector, a Storefront context viewer, and Vue DevTools.
14
14
  </p>
15
15
  <p align="center">
16
16
  <a href="#"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="SCAYLE's Storefront DevTools is released under the MIT license." /></a>
@@ -18,30 +18,29 @@
18
18
 
19
19
  ## Overview
20
20
 
21
- `@scayle/storefront-devtools` is a dev-only Vite plugin. It hosts the Vite DevTools dock and registers a **SCAYLE Storefront** dock group, plus a **Vue** group:
21
+ `@scayle/storefront-devtools` is a dev-only Vite plugin. It hosts the Vite DevTools dock and registers two dock groups, **SCAYLE Storefront** and **Vue**:
22
22
 
23
- - **Inertia.js panel**: every visit your app makes, correlated into one request per navigation. Shows the response body and headers, the Inertia protocol details (partial reload keys, asset version, redirects), and a lifecycle timeline, with diagnostics for common mistakes like a missing partial-reload prop. The [Inertia.js panel](#inertiajs-panel) section covers it in depth.
24
- - **oRPC panel**: every call to your `/api` endpoint, decoded from its wire format back to typed input and output. Groups calls by procedure, classifies failures, and tracks per-procedure timing. The [oRPC panel](#orpc-panel) section covers it in depth.
25
- - **Storefront context panel**: a read-only view of the resolved shop, country, locale, and session for the page you're looking at, read from the Inertia page the SDK ships. The [Storefront context panel](#storefront-context-panel) section covers it.
26
- - **Resource Center**: the Storefront documentation, embedded in the dock so you don't have to leave the app to look something up.
27
- - **Vue panel**: the component tree and timeline you'd otherwise get from a separate `vite-plugin-vue-devtools` install, mounted in the same dock.
23
+ - The Inertia.js panel lists every visit your app makes, one request per navigation. It shows the response body and headers, the Inertia protocol details (partial reload keys, asset version, redirects), and a lifecycle timeline, and it flags common mistakes such as a missing partial-reload prop. The [Inertia.js panel](#inertiajs-panel) section covers it in depth.
24
+ - The oRPC panel lists every call to your `/api` endpoint and decodes each call's `{ json, meta }` envelope back to typed input and output. It groups calls by procedure, classifies failures, and tracks timing per procedure. The [oRPC panel](#orpc-panel) section covers it in depth.
25
+ - The Storefront context panel is a read-only view of the resolved shop, country, locale, and session for the page you're looking at, taken from the Inertia page the SDK ships. The [Storefront context panel](#storefront-context-panel) section covers it.
26
+ - The Vue panel gives you the component tree and timeline in the same dock, so you don't need a separate `vite-plugin-vue-devtools` install.
28
27
 
29
- The plugin runs only on the dev server and drops out of production builds with no environment flag needed — see "Production safety" below for exactly why.
28
+ The plugin only runs on the dev server and drops out of production builds without an environment flag. [Production safety](#production-safety) explains why.
30
29
 
31
- ## Why not the browser Network tab?
30
+ ## Beyond the browser Network tab
32
31
 
33
- The browser's Network tab is the right tool for raw HTTP. These panels exist for what it structurally cannot show.
32
+ Use the browser's Network tab for raw HTTP. These panels cover what it can't show.
34
33
 
35
- - **Inertia visits often have no network footprint.** The initial page load embeds the page object in the HTML, so it is never an XHR. Client-side history navigations, prefetch cache hits, and `preserveState` updates produce no request at all. The Network tab has no row for any of these. The panel captures them from Inertia's DOM events and history, so they are visible.
36
- - **The panel diffs and correlates across visits.** It shows a structural props diff between consecutive visits, links a deferred-prop reload to the parent visit that triggered it, and renders the lifecycle event timeline. The Network tab shows one request at a time with no memory of the last one.
37
- - **oRPC bodies on the wire are not your arguments.** Every oRPC call is a `POST /api` carrying a `{ json, meta }` envelope. The Network tab shows that envelope raw and cannot reconstruct the `Date`, `Map`, `Set`, or `BigInt` values it encodes, because JSON has no way to represent them. The panel decodes the envelope to the typed value your handler actually received.
38
- - **oRPC calls are indistinguishable in the Network tab.** They are all POSTs to the same `/api` URL, so you cannot tell `basket/add` from `wishlist/remove` without opening each one and reading the body. The panel groups by procedure, aggregates per-procedure timing, and classifies each failure as validation, server, or network.
34
+ - Many Inertia visits never hit the network. The initial page load embeds the page object in the HTML, so no XHR fires. Client-side history navigations, prefetch cache hits, and `preserveState` updates send no request at all, and the Network tab has no row for them. The Inertia.js panel picks them up from Inertia's DOM events and from history.
35
+ - The Inertia.js panel compares visits. It diffs the props of consecutive visits, links a deferred-prop reload to the visit that triggered it, and draws the lifecycle event timeline. The Network tab shows one request at a time and forgets the one before.
36
+ - An oRPC request body isn't the argument your code passed. Every oRPC call is a `POST` under `/api` with a `{ json, meta }` envelope. The Network tab shows that envelope as raw JSON and can't rebuild the `Date`, `Map`, `Set`, or `BigInt` values it encodes, because JSON has no way to represent them. The oRPC panel decodes the envelope back to the typed value your handler received.
37
+ - oRPC calls get lost in the Network tab, between the images, scripts, and page visits a storefront loads. The oRPC panel lists only oRPC calls, groups them by procedure, adds up the timing per procedure, and classifies each failure as validation, server, or network.
39
38
 
40
- Where it overlaps the Network tab (the per-call HTTP view of a single oRPC request), the panels do not try to replace it. Use the Network tab for the raw transaction, the panels for the framework-level meaning.
39
+ Where the two overlap, in the HTTP view of a single oRPC request, the panels don't try to replace the Network tab. Use the Network tab for the raw transaction and the panels for what it means to Inertia and oRPC.
41
40
 
42
41
  ## Installation
43
42
 
44
- The package ships with Storefront Application V3 by default. To add it manually:
43
+ The package ships with Storefront Application V3 by default. To add it yourself:
45
44
 
46
45
  ```bash
47
46
  pnpm add -D @scayle/storefront-devtools
@@ -49,7 +48,7 @@ pnpm add -D @scayle/storefront-devtools
49
48
 
50
49
  ## Integration
51
50
 
52
- The package is consumed only from the Storefront Application V3 `vite.config.ts`. No other application code changes are required: the dock client and the Inertia and oRPC capture clients are injected into the application's client entry automatically.
51
+ The only file you change is the Storefront Application V3 `vite.config.ts`. The plugin injects the dock client and the Inertia and oRPC page scripts into the app's client entry, so the rest of your app code stays as it is.
53
52
 
54
53
  Import the default export and spread it into the `plugins` array, after `@vitejs/plugin-vue`:
55
54
 
@@ -75,147 +74,178 @@ export default defineConfig({
75
74
  })
76
75
  ```
77
76
 
78
- `storefrontDevtools()` returns an array of Vite plugins, so it must be spread. It also provides the Vue DevTools panel itself, so no separate `vite-plugin-vue-devtools` is needed.
77
+ `storefrontDevtools()` returns an array of Vite plugins, so you have to spread it. It includes the Vue DevTools panel, so you don't need `vite-plugin-vue-devtools` on top.
79
78
 
80
79
  ### Dev server path exclusion
81
80
 
82
- The dock and its assets are served by Vite under `/__devtools/` and `/__storefront-devtools/`. The Storefront Application V3 serves pages through a Hono dev server with a country-prefix redirect, which would otherwise catch those paths and rewrite them. `@scayle/storefront-build` already excludes the DevTools mount paths from the Hono dev server, so no extra configuration is needed when both packages are present.
81
+ Vite serves the dock and its assets under `/__devtools/` and `/__storefront-devtools/`. The Storefront Application V3 serves pages through a Hono dev server with a country-prefix redirect, which would catch those paths and rewrite them. `@scayle/storefront-build` already excludes the DevTools paths from the Hono dev server, so with both packages installed you need no extra configuration.
83
82
 
84
- `storefrontDevtools()` takes no options. It always registers the Inertia.js, oRPC, Storefront context, Resource Center, and Vue panels, and always injects into the Storefront Application V3's own client entry (`src/client/main.ts`).
83
+ `storefrontDevtools()` takes no options. It always registers the Inertia.js, oRPC, Storefront context, and Vue panels, and it always injects into the Storefront Application V3's own client entry (`src/client/main.ts`).
85
84
 
86
85
  ## Opening the dock
87
86
 
88
- Run the Storefront Application V3 dev server (`pnpm dev`) and open the app. A floating launcher appears on the page. Open it, then open the **SCAYLE Storefront** group to reach the **Inertia.js**, **oRPC**, **Storefront**, and **Resource Center** entries, or the **Vue** group for the component tree and timeline.
87
+ Run the Storefront Application V3 dev server (`pnpm dev`) and open the app. A floating launcher appears on the page. Open it, then open the **SCAYLE Storefront** group for the **Inertia.js**, **oRPC**, and **Storefront** entries, or the **Vue** group for the component tree and timeline.
89
88
 
90
- The launcher is injected into the client entry (not the HTML) because the Storefront Application V3 composes its HTML through the SSR renderer, so Vite's `transformIndexHtml` hook never fires. The same mechanism injects every panel's capture client.
89
+ The plugin injects the launcher into the client entry instead of the HTML. The Storefront Application V3 builds its HTML in the SSR renderer, so Vite's `transformIndexHtml` hook never fires. Every panel's page script arrives through the same injection.
91
90
 
92
- ## Security: how the dock authenticates the browser
91
+ ## Security and browser authorization
93
92
 
94
- The dock's browser side and the Vite dev server talk over a WebSocket RPC channel (`@devframes/hub`, using `crossws` as its transport abstraction). That channel is not limited to rendering our panels — `@vitejs/devtools` ships its own built-in RPC surface with real capabilities on the developer's machine, most notably **`vite:core:open-in-editor`** (opens an arbitrary file path in the developer's editor) and a terminals feed. This package sets `builtinDevTools: false`, which drops the upstream Terminals/Messages/Inspector docks and their UI, but the underlying RPC declarations are part of `@vitejs/devtools` itself, not something a consumer can un-register from the wire protocol. Two independent gates protect this channel:
93
+ The dock in the browser talks to the Vite dev server over a WebSocket RPC channel (`@devframes/hub`, with `crossws` as its transport layer). Our panels aren't the only thing on that channel. `@vitejs/devtools` ships its own RPC functions with real reach into your machine, for example `vite:core:open-in-editor`, which opens any file path in your editor, and a terminals feed. This package sets `builtinDevTools: false`, which drops the upstream Terminals, Messages, and Inspector docks and their UI. The RPC declarations behind them stay, because they belong to `@vitejs/devtools` itself and a consumer can't remove them. Two independent checks protect the channel.
95
94
 
96
- - **Origin allowlist.** `@vitejs/devtools` only accepts WebSocket connections whose `Origin` header matches a built-in loopback allowlist (`localhost`, `127.0.0.1`, and similar). This check is a **plain string match, not a DNS or hosts-file resolution** — a custom dev hostname that resolves to loopback via `/etc/hosts` or a local DNS override (e.g. `dev-my-app.example.com`) is invisible to it and gets rejected by default. If your setup needs a non-default hostname (a reverse proxy, a tunnel, a custom `/etc/hosts` entry), list it explicitly via `devtools: { allowedOrigins: ['https://dev-my-app.example.com'] }` in the app's Vite config.
97
- - **One-time client authorization.** On top of the origin check, `@vitejs/devtools` gates the RPC connection behind a one-time code by default (`clientAuth: true`). On first connection from a new browser, the `pnpm dev` terminal prints `devframe auth code <code>` and a magic-link URL (`...#devframe_otp=<code>`); enter the code in the dock's prompt, or open the printed URL, to authorize that browser. Until then, the dock stays unauthorized — this is expected, not a connection failure.
95
+ ### Origin allowlist
98
96
 
99
- **Disabling the authorization gate.** Setting `devtools: { clientAuth: false }` (or the `VITE_DEVTOOLS_DISABLE_CLIENT_AUTH=true` env var) skips the one-time-code prompt. Per `@vitejs/devtools`'s own documentation: _"if you disable client authentication, any browsers can connect to the devtools and access to your server and filesystem (including other devices, if you open server `host` option to LAN or WAN)."_ Only disable this in an environment where every request already reaches only a trusted developer (never with `server.host` bound to a LAN/WAN interface), and never in anything resembling a shared or production-adjacent environment.
97
+ `@vitejs/devtools` accepts a WebSocket connection only when its `Origin` header matches a built-in loopback allowlist (`localhost`, `127.0.0.1`, and similar). The check compares strings. It doesn't resolve DNS or read your hosts file, so a custom dev hostname that points at loopback through `/etc/hosts` or a local DNS override (for example `dev-my-app.example.com`) gets rejected by default. If your setup needs another hostname, such as a reverse proxy, a tunnel, or a custom `/etc/hosts` entry, add it with `devtools: { allowedOrigins: ['https://dev-my-app.example.com'] }` in the app's Vite config.
100
98
 
101
- **Production safety.** `storefrontDevtools()` always registers its plugins, with no `import.meta.env.PROD` check on our side — the safety comes from `@vitejs/devtools` itself. The plugin that actually opens the WebSocket RPC server does so from Vite's `configureServer` hook, which Vite only ever calls during `vite dev`/`vite serve`, never during `vite build`. A production build and its runtime output carry no RPC server, no WebSocket endpoint, and none of the injected capture/launcher code reachable from a deployed Storefront Application V3.
99
+ ### One-time client authorization
102
100
 
103
- ## How it works
101
+ On top of the origin check, `@vitejs/devtools` puts the RPC connection behind a one-time code by default (`clientAuth: true`). The first time a new browser connects, the `pnpm dev` terminal prints `devframe auth code <code>` and a magic-link URL (`...#devframe_otp=<code>`). Enter the code in the dock's prompt, or open the printed URL, to authorize that browser. The dock stays unauthorized until you do, and that's expected.
104
102
 
105
- The package has two halves, both running in the host page's browser context, connected through a `window` global. There is no iframe between them: the Inertia panel mounts directly into the dock's panel element (a custom-render entry) and reads the capture store off `window`.
103
+ ### Turning off the authorization check
104
+
105
+ Setting `devtools: { clientAuth: false }` (or the `VITE_DEVTOOLS_DISABLE_CLIENT_AUTH=true` environment variable) skips the one-time code. The `@vitejs/devtools` documentation warns about this: _"if you disable client authentication, any browsers can connect to the devtools and access to your server and filesystem (including other devices, if you open server `host` option to LAN or WAN)."_ Only turn it off where every request comes from a trusted developer. Never do it with `server.host` bound to a LAN or WAN interface, or in a shared environment or anything close to production.
106
+
107
+ ### Production safety
108
+
109
+ `storefrontDevtools()` registers its plugins in every build, with no `import.meta.env.PROD` check of its own. The safety comes from `@vitejs/devtools`. Its plugin opens the WebSocket RPC server from Vite's `configureServer` hook, and Vite calls that hook during `vite dev` and `vite serve`, never during `vite build`. A production build and its runtime output carry no RPC server, no WebSocket endpoint, and none of the injected page scripts or launcher code.
110
+
111
+ ## Page scripts and panel apps
112
+
113
+ The package has two halves. Page scripts run in the app's own page and capture what happens there. Panel apps run in iframes inside the dock and render what the page scripts publish. The two talk over `devframe/in-page-channel`, a `postMessage` channel between the page and the iframes the dock hosts for it.
106
114
 
107
115
  ```
108
- host page (browser)
109
- injected client (into the app's client entry) dock custom-render
110
- DOM-event capture (inertia:*) Vue panel
111
- -> Correlator -> RequestRecords reads & mounted into the dock DOM
112
- history-capture (pushState/replaceState) ───────▶ subscribes to the store
116
+ app page (browser) dock iframe
117
+ page script (injected into the client entry) panel app (/__storefront-devtools/panels/)
118
+ DOM-event capture (inertia:*) ?view=inertia | orpc | storefront
119
+ -> Correlator -> RequestRecords
120
+ history-capture (pushState/replaceState)
113
121
  network timing (PerformanceObserver)
114
- -> DevToolsStore on window
115
- window.__STOREFRONT_DEVTOOLS_STORE__
122
+ -> DevToolsStore
123
+ createPageScriptChannel() ── state snapshots ──▶ connectPanelChannel()
124
+ ◀── getState, clear, mirrors the latest
125
+ setPaused ─────── snapshot into a Vue ref
116
126
  ```
117
127
 
118
- ### Capture (host page)
128
+ ### Capture in the app page
119
129
 
120
- On load, the injected client builds a `DevToolsStore`, stashes it at `window.__STOREFRONT_DEVTOOLS_STORE__`, and wires three non-invasive sources into it:
130
+ On load, the Inertia page script builds a `DevToolsStore` and feeds it from three sources, none of which change how Inertia behaves:
121
131
 
122
- - **DOM events** — `document.addEventListener` over the Inertia 3.x `inertia:*` events. Each event's detail is safely serialized (depth-limited, functions and abort signals stripped) and fed to the store. No `router.on()` patching.
123
- - **Network timing** — a `PerformanceObserver` over resource-timing entries, matched to in-flight requests by URL.
124
- - **Client-side history** — a `history.pushState` / `replaceState` patch that detects client visits (`router.push` / `router.replace`) that bypass the normal Inertia event lifecycle.
132
+ - Inertia 3.x's `inertia:*` DOM events, through `document.addEventListener`. The script copies each event's detail with a depth limit, drops functions and abort signals, and hands it to the store. It doesn't patch `router.on()`.
133
+ - A `PerformanceObserver` over resource-timing entries, matched to in-flight requests by URL.
134
+ - A `history.pushState` / `replaceState` patch that catches client visits (`router.push` / `router.replace`) that skip the normal Inertia event lifecycle.
125
135
 
126
- Every handler is wrapped so the devtools can never crash the host application.
136
+ Every handler runs inside a guard, so a devtools error can't crash the app.
127
137
 
128
138
  ### Correlation
129
139
 
130
- A correlator groups the raw events into one `RequestRecord` per logical visit, matching events by a visit fingerprint plus an active-visit pointer. It handles deferred prop reloads, prefetch cache hits, POST→redirect flows, 409 asset-version mismatches, and the initial page load. Records are kept in a bounded buffer (200), and a compact summary of the previous session is persisted to `sessionStorage` so requests from before a reload remain visible.
140
+ A correlator groups the raw events into one `RequestRecord` per visit. It matches events by a visit fingerprint plus a pointer to the active visit, and it handles deferred prop reloads, prefetch cache hits, POST→redirect flows, 409 asset-version mismatches, and the initial page load. The store keeps the latest 200 records and saves a compact summary to `sessionStorage`, so the requests from before a reload stay visible.
131
141
 
132
142
  ### oRPC capture
133
143
 
134
- The oRPC panel follows the same shape with its own store on `window.__STOREFRONT_DEVTOOLS_ORPC_STORE__`. Its injected client patches `window.fetch` once on load. Each request whose path starts with `/api` is recorded as one `OrpcCall`: the procedure name (the path after `/api`), the JSON input read from the request body, and on response the JSON output, the status, the duration, and any error. Every other fetch passes through untouched, and the original response or rejection always reaches the app unchanged, so the patch can never break a real request. The store caps the buffer at 200, classifies each failure as validation, server, or network, and persists a previous-session summary like the Inertia store. The wire JSON is captured as-is, not the decoded oRPC values.
144
+ The oRPC panel follows the same shape with its own store and page script. The page script patches `window.fetch` once on load. For each request whose path starts with `/api`, it records one `OrpcCall`: the procedure name (the path after `/api`), the input from the request body, and, once the response arrives, the output, status, duration, and any error. Every other fetch passes through untouched, and the app always gets the original response or rejection, so the patch can't break a real request. The store keeps the latest 200 calls, classifies each failure as validation, server, or network, and saves a previous-session summary the way the Inertia store does. It keeps each input, output, and error in two forms: decoded to typed values, and as the raw `{ json, meta }` envelope, which the Data tab can show too.
145
+
146
+ ### Publishing to the panels
147
+
148
+ Each page script opens its own channel (`storefront-devtools:inertia`, `storefront-devtools:orpc`) and pushes a snapshot of its store to the connected panels whenever the store changes. It sends at most one snapshot per 16 ms, so a burst of Inertia events costs one message instead of twenty. A timer paces this rather than `requestAnimationFrame`, because the browser pauses animation frames in a hidden tab, and the dock can sit in a Picture-in-Picture window while the app's tab is in the background. A panel that opens later asks for the current snapshot with `getState`. **Pause** and **Clear** reach the store the same way, as channel calls.
149
+
150
+ Snapshots cross the channel through structured clone, which rejects functions, so the page scripts run captured values through `safeClone` before they store them. The channel stays inside one browser tab, so two open tabs never see each other's requests.
135
151
 
136
152
  ### Storefront context
137
153
 
138
- Unlike the Inertia and oRPC panels, the Storefront context panel has no capture client of its own: it reads the live Inertia page directly from the Inertia panel's store (`window.__STOREFRONT_DEVTOOLS_STORE__`), so it only shows data that store has already captured.
154
+ Unlike the Inertia and oRPC panels, the Storefront context panel has no page script of its own. The Inertia page script serves it on a second channel, `storefront-devtools:storefront`, which carries only the current Inertia page and sends a new snapshot only when that page changes. The panel shows what the Inertia store has already captured, nothing more.
139
155
 
140
- ### Mounting a panel into the dock
156
+ ### Panel apps
141
157
 
142
- Every custom-render panel (Inertia, oRPC, Storefront context) mounts through a shared `mountPanel()` helper, not a naive `dom:panel:mounted` listener. The dock's `@devframes/hub-ui` runtime switches to a panel entry and assigns its DOM element (which internally fires `dom:panel:mounted`) _before_ it `import()`s the panel's renderer module, so a listener registered inside the renderer never sees that event fire. `mountPanel()` checks the panel element synchronously first, falling back to the event listener only if it isn't set yet — the same pattern `@devframes/hub-ui` uses internally for its own iframe entries.
158
+ The dock loads every panel as an `iframe` entry. The package builds one small Vue app for the three storefront panels and serves it, together with the Vue DevTools UI, from `/__storefront-devtools/` on the dev server. The `?view=` query picks the panel the app mounts. Each iframe is its own document, so the panel styles never touch the app, and the panel's own Vue instance stays out of the app's Vue DevTools. The dock keeps a hidden panel's iframe alive while you use another entry, and that panel keeps receiving snapshots.
143
159
 
144
160
  ## Panels
145
161
 
146
162
  ### Inertia.js panel
147
163
 
148
- This panel is the reason the package exists. Inertia drives navigation over XHR and swaps page props without a full reload, so the browser network tab shows you raw JSON and nothing about the visit itself. The panel reads the Inertia lifecycle directly and shows you the visit.
164
+ Inertia drives navigation over XHR and swaps page props without a full reload, so the browser Network tab shows you raw JSON and nothing about the visit itself. This panel reads the Inertia lifecycle and shows you the visit. It's the main reason the package exists.
165
+
166
+ It opens on the **Requests** tab and has a second **Page** tab. A top bar holds **Pause** and **Clear**.
149
167
 
150
- It opens on the **Requests** tab and has a second **Page** tab. A top bar carries **Pause** and **Clear**.
168
+ #### Requests tab
151
169
 
152
- **Requests tab.** The left side lists every visit as it happens: method, URL, visit type, timing, and a badge for any diagnostics. Filter the list by type (full, partial, deferred, prefetch, redirect, client) or type a string into the URL box to narrow it. Deferred reloads nest under the visit that triggered them, so you can see which page kicked off which background load. When the buffer fills past 200 visits a footer tells you how many older ones rolled off. Reload the page and a collapsible **Previous session** block keeps the visits from before the reload, which matters because a full reload normally wipes the list. Use this tab to answer "did that click even fire a visit, and what kind."
170
+ The left side lists each visit as it happens: method, URL, visit type, timing, and a badge for any diagnostics. Filter by type (full, partial, deferred, prefetch, redirect, client), or type into the URL box to narrow the list. Deferred reloads nest under the visit that triggered them, so you can see which page started which background load. Once the list passes 200 visits, a footer tells you how many older ones dropped off. After a reload, a collapsible **Previous session** block keeps the visits from before it, because a full reload would otherwise empty the list. This tab answers "did that click fire a visit, and what kind?"
153
171
 
154
- Select a visit and the right side opens its detail across four tabs:
172
+ Select a visit and the right side shows its detail in four tabs:
155
173
 
156
- - **Response** — the response props actually captured for that visit, each tagged with how it arrived (full, partial, deferred, merged, prepended, or deep-merged), plus a note for any prop you requested via `only` that the response omitted, and a count of props carried over unchanged from the previous page. A **Show changes since last page** toggle switches to a before/after diff instead, marking added keys, removed keys, and changed values. This is where you confirm a partial or deferred reload returned the prop you asked for, or spot a prop that changed when it should not have.
157
- - **Headers** — the request and response headers actually sent and received for that visit, as a table. Credential-bearing headers (`authorization`, `cookie`, and similar) are redacted before they are ever stored.
158
- - **Network** — the Inertia protocol view of the request: asset version, the `only` / `except` partial-reload keys, the `errorBag` name, reset keys, plus resource timing and transfer size when the browser recorded them. Use it to verify a partial reload sent the right `only` set, or to see how long a visit actually took on the wire.
159
- - **Events** — a lifecycle timeline (`before`, `start`, `success`, `finish`, and the rest) with each step's offset in milliseconds from the first event, color-coded by outcome. Below it, every captured event with its full payload as a collapsible tree. This is where you debug a visit that hung, was canceled, or fired events in an order you did not expect.
174
+ - **Response** shows the props captured for that visit, each tagged with how it arrived (full, partial, deferred, merged, prepended, or deep-merged). It notes any prop you asked for with `only` that the response left out, and counts the props carried over unchanged from the previous page. The **Show changes since last page** toggle switches to a before/after diff that marks added keys, removed keys, and changed values. Use it to confirm a partial or deferred reload returned the prop you asked for, or to spot a prop that changed when it shouldn't have.
175
+ - **Headers** shows the request and response headers of that visit as a table. The store redacts credential headers (`authorization`, `cookie`, and similar) before it saves anything.
176
+ - **Network** shows the Inertia protocol side of the request: asset version, the `only` / `except` partial-reload keys, the `errorBag` name, reset keys, and resource timing and transfer size when the browser recorded them. Use it to check that a partial reload sent the right `only` set, or to see how long a visit spent on the network.
177
+ - **Events** shows a lifecycle timeline (`before`, `start`, `success`, `finish`, and the rest) with each step's offset in milliseconds from the first event, colored by outcome. Below the timeline sits every captured event with its full payload as a collapsible tree. Come here to debug a visit that hung, got canceled, or fired events in an order you didn't expect.
160
178
 
161
- The detail header also carries **Copy as Markdown**, which copies the visit (header, features, error, and the raw page JSON) to your clipboard for pasting into an issue or an AI chat.
179
+ The detail header also has **Copy as Markdown**, which copies the visit (header, features, error, and the raw page JSON) for pasting into an issue or an AI chat.
162
180
 
163
- Above the tabs, the detail shows an overview (method, URL, type, status, component, duration, and the redirect URL on a 409), the active **feature badges**, and any **diagnostics**.
181
+ Above the tabs, the detail shows an overview (method, URL, type, status, component, duration, and the redirect URL on a 409), the active feature badges, and any diagnostics.
164
182
 
165
- **Feature badges** mark the Inertia features active on a visit: partial reload, deferred props, the merge / deep-merge / prepend strategies, scroll regions, once props, prefetch, cached (a prefetch hit that skipped the network), flash data, remembered state, encrypted history, and clear-history. Click a badge to expand its description and a link to the matching Inertia docs, so you do not need to remember what each one means.
183
+ #### Feature badges
166
184
 
167
- **Diagnostics** flag common mistakes on a visit: an asset-version mismatch that forced a 409 reload, a canceled or interrupted visit, a partial-reload prop you requested but the response omitted, validation errors dropped because an `only` option left out `errors`, deferred props that failed to load, and a same-URL history replace. Each links to the relevant docs.
185
+ Feature badges mark the Inertia features active on a visit: partial reload, deferred props, the merge, deep-merge, and prepend strategies, scroll regions, once props, prefetch, cached (a prefetch hit that skipped the network), flash data, remembered state, encrypted history, and clear-history. Click a badge to see its description and a link to the matching Inertia docs, so you don't have to remember what each one means.
168
186
 
169
- **Page tab.** The current page props as a collapsible tree, with the page-level feature badges. Use it to inspect the props your component is rendering right now, separate from any one visit.
187
+ #### Diagnostics
170
188
 
171
- **Pause** stops capture so a busy page stops flooding the list while you read a visit. Resume picks up new visits from that point. **Clear** empties the list. Your active tab and list filter persist across panel reopens, the URL search does not.
189
+ Diagnostics flag common mistakes on a visit: an asset-version mismatch that forced a 409 reload, a canceled or interrupted visit, a partial-reload prop you requested that the response left out, validation errors lost because an `only` option didn't include `errors`, deferred props that failed to load, and a history replace to the same URL. Each one links to the relevant docs.
190
+
191
+ #### Page tab
192
+
193
+ The Page tab shows the current page props as a collapsible tree, with the page-level feature badges. Use it to inspect the props your component renders right now, apart from any single visit.
194
+
195
+ #### Pausing and clearing
196
+
197
+ Pause stops capture, so a busy page doesn't flood the list while you read a visit. Resume picks up new visits from that point. Clear empties the list. The panel keeps your active tab and list filter when you reopen it, but not the URL search.
172
198
 
173
199
  ### oRPC panel
174
200
 
175
- The Storefront Application V3 mutates state through oRPC calls to `/api`: add to basket, toggle wishlist, apply a promotion, run a search. In the browser network tab these all look like identical POSTs to `/api`, with the procedure buried in the request body. This panel reads them as oRPC calls and labels each by its procedure.
201
+ The Storefront Application V3 changes state through oRPC calls to `/api`: add to basket, toggle wishlist, apply a promotion code, run a search. In the browser Network tab they show up as POST requests with a JSON envelope for a body, mixed in with everything else the page loads. This panel shows them as oRPC calls, labeled by procedure.
176
202
 
177
- It opens on the **Calls** tab and has a second **Stats** tab. A top bar carries **Pause** and **Clear**.
203
+ It opens on the **Calls** tab and has a second **Stats** tab. A top bar holds **Pause** and **Clear**.
178
204
 
179
- **Calls tab.** The left side lists every oRPC call as it fires: method, procedure (`basket/add`, `wishlist/remove`, and so on), a status badge colored green for success, amber for a validation error, red for a server or network error, and the duration. Filter the list by domain (the first path segment, `basket`, `wishlist`, `search`) or type a string into the box to match the procedure. A collapsible **Previous session** block keeps the calls from before a reload.
205
+ #### Calls tab
180
206
 
181
- Select a call and the right side opens its detail across two tabs:
207
+ The left side lists each oRPC call as it fires: method, procedure (`basket/add`, `wishlist/remove`, and so on), a status badge, and the duration. The badge is green for success, amber for a validation error, and red for a server or network error. Filter by domain (the first path segment, such as `basket`, `wishlist`, or `search`), or type into the box to match a procedure. A collapsible **Previous session** block keeps the calls from before a reload.
182
208
 
183
- - **Data** — the request input as a collapsible tree, and the success output as a collapsible tree, or, when the call failed, the **Error** with its oRPC code, status, message, and data. A streaming response shows a note instead of a buffered body.
184
- - **Headers** — the request and response headers actually sent and received for that call, as a table. Credential-bearing headers and sensitive input fields (passwords, tokens, and similar) are redacted before they are ever stored.
209
+ Select a call and the right side shows its detail in two tabs:
185
210
 
186
- The overview above the tabs shows the procedure, method, URL, status, duration, and the error kind when the call failed.
211
+ - **Data** shows the request input and the success output as collapsible trees. For a failed call it shows the **Error** instead, with its oRPC code, status, message, and data. A streaming response shows a note in place of a buffered body.
212
+ - **Headers** shows the request and response headers of that call as a table. The store redacts credential headers and sensitive input fields (passwords, tokens, and similar) before it saves anything.
187
213
 
188
- The detail header carries **Copy as curl**, which copies the call as a runnable `curl` command with the JSON body, for replaying the request in a terminal or pasting into an issue.
214
+ The overview above the tabs shows the procedure, method, URL, status, duration, and the error kind for a failed call.
189
215
 
190
- **Stats tab.** A table of every procedure with its call count, average duration, and last duration. Sort by count to find the chatty procedure a page fires ten times, or by average to find the slow one. Use it to answer "which call is making this page feel slow."
216
+ The detail header has **Copy as curl**, which copies the call as a runnable `curl` command with its JSON body, for replaying it in a terminal or pasting it into an issue.
191
217
 
192
- **Pause** stops capture while you read a call. **Clear** empties the list.
218
+ #### Stats tab
193
219
 
194
- ### Storefront context panel
220
+ The Stats tab lists every procedure with its call count, average duration, and last duration. Sort by count to find the chatty procedure a page fires ten times, or by average to find the slow one.
221
+
222
+ #### Pausing and clearing
195
223
 
196
- A read-only view of the resolved shop, country, locale, and session for the current page, plus the config public fields the SDK exposes to the client. It never renders secrets or the full session token, only what the Inertia page props already contain. Use it to confirm which shop/country/locale the app resolved for a request without hunting through page props by hand.
224
+ Pause stops capture while you read a call. Clear empties the list.
197
225
 
198
- ### Resource Center
226
+ ### Storefront context panel
199
227
 
200
- A plain iframe that embeds the Storefront documentation in the dock, so you can look something up without leaving the app or hunting for a tab.
228
+ A read-only view of the resolved shop, country, locale, and session for the current page, plus the config public fields the SDK sends to the client. It shows only what the Inertia page props already contain, so it never renders secrets or the full session token. Use it to check which shop, country, and locale the app resolved for a request without digging through page props.
201
229
 
202
230
  ### Vue
203
231
 
204
- The Vue DevTools component tree and timeline, in a **Vue** dock group alongside the SCAYLE Storefront group. It runs as a standalone iframe app (not a custom-render panel like the others) connected to the host page over a `BroadcastChannel` RPC bridge, so it works without the dock owning an `<iframe>` element the applet controls directly. Replaces a separate `vite-plugin-vue-devtools` install.
232
+ The Vue DevTools component tree and timeline, in a **Vue** dock group next to the SCAYLE Storefront group. It replaces a separate `vite-plugin-vue-devtools` install. Every panel in this package runs as an iframe app. The Vue panels load the `@vue/devtools-applet` UI, which reaches the app page over a `BroadcastChannel` RPC bridge. The storefront panels use the package's own panel app.
205
233
 
206
- Click-to-inspect (clicking a rendered element to jump to its component in the tree) is not included: it needs `vite-plugin-vue-inspector` wired into the app's own Vite config, which this package does not add.
234
+ Click-to-inspect, where you click a rendered element to jump to its component in the tree, isn't included. It needs `vite-plugin-vue-inspector` in the app's own Vite config, and this package doesn't add it.
207
235
 
208
236
  ## Architecture notes
209
237
 
210
- Each panel that captures live app state (Inertia, oRPC) separates a framework-agnostic core (plain TypeScript, no Vue) from its Vue panel. The Inertia panel lives in `src/inertia/core` and `src/inertia/panel`. The oRPC panel mirrors it in `src/orpc/core` and `src/orpc/panel`. Both reuse shared panel UI (`TreeView`, the clipboard helper, panel styles) and shared capture-side generics (a `window`-store polling hook, a `sessionStorage` persistence helper) from `src/shared/`. The core handles capture, classification, diffing, diagnostics, and persistence, and the panel renders it. The panel styles are dark-mode only and are injected into the dock's shadow root, so they never leak into the host application.
238
+ Each panel that captures live app state (Inertia, oRPC) splits into three parts. `src/<feature>/core/` is plain TypeScript with no Vue import, and handles capture, classification, diffing, diagnostics, and persistence. `src/<feature>/client/` is the page script: it runs the core in the app page and publishes the store over `devframe/in-page-channel`. `src/<feature>/panel/` holds the Vue components that render it. Both features share panel UI (`TreeView`, the clipboard helper, panel styles) and generic building blocks (snapshot pacing, snapshot mirroring, `sessionStorage` persistence) from `src/shared/`.
239
+
240
+ The storefront panels load from `/__storefront-devtools/panels/`, one iframe per view, and the Vue DevTools UI loads from `/__storefront-devtools/vue/`. The panel styles are dark-mode only. Each panel runs in its own iframe document, so its styles never leak into the host application.
211
241
 
212
242
  ## Upstream stability
213
243
 
214
- `@vitejs/devtools`, `@vitejs/devtools-kit`, `@devframes/hub`, and `@devframes/hub-ui` are all pre-1.0 (`0.x`) releases. There is no published semver stability guarantee for 0.x versions of these packages — any minor version can carry a breaking change with no deprecation window, and neither project's changelog consistently flags every breaking change as such. If you upgrade any of them:
244
+ `@vitejs/devtools` and `@vitejs/devtools-kit` are still `0.x` releases, so any minor version can ship a breaking change without a deprecation period. `@devframes/hub` and `@devframes/hub-ui` reached 1.0.0 in September 2026. If you upgrade any of them:
215
245
 
216
- - Check `@vitejs/devtools`'s peer range against the Vite version this repository pins — `@vitejs/devtools` 0.6.x and 0.7.x have shipped outside `vite`'s own declared peer range for `@vitejs/devtools` at times, so a newer devtools version is not automatically compatible with an older (or even the current) pinned Vite.
217
- - `@devframes/hub`/`@devframes/hub-ui` resolve as transitive dependencies of `@vitejs/devtools`, so a routine `pnpm update` (or a fresh install after a range widens) can move them independently of any `@vitejs/devtools` version bump you intended.
218
- - Re-run `pnpm exec vitest run`, `pnpm run build`, `pnpm run verify-packaging`, and a live check of all five panels in the dock after any bump — the unit tests do not exercise the dock's actual runtime, only this package's own capture/classification logic.
246
+ - Check the peer range `@vitejs/devtools` declares against the Vite version this repository pins. `@vitejs/devtools` 0.6.x and 0.7.x have at times shipped outside the range `vite` itself declares for it, so a newer devtools version doesn't always work with the pinned Vite.
247
+ - `@devframes/hub` and `@devframes/hub-ui` are transitive dependencies of `@vitejs/devtools`. A routine `pnpm update`, or a fresh install after a range widens, can move them without any `@vitejs/devtools` bump.
248
+ - After any bump, run `pnpm exec vitest run`, `pnpm run build`, and `pnpm run verify-packaging` again, then open every panel in the dock. The unit tests cover this package's capture and classification logic, not the dock's runtime.
219
249
 
220
250
  ## License
221
251