@zerotal/devtools 1.7.3 → 1.7.4
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.
- package/CHANGELOG.md +16 -0
- package/package.json +3 -3
- package/src/client/index.ts +62 -16
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,22 @@ follows the Zerotal monorepo's unified versioning.
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [1.7.4] — 2026-08-21
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **The panel no longer mounts when there is no server half to talk to.** The provider is
|
|
16
|
+
gated on the environment, so in production the devtools routes are absent — and the client
|
|
17
|
+
read that as permission to start anyway and "connect to nothing". It did not connect to
|
|
18
|
+
nothing: `DevTools.start()` mounted the panel first and discovered the absence afterwards,
|
|
19
|
+
so an app calling it unconditionally served a floating DevTools bar to every visitor, its
|
|
20
|
+
tabs reading `Could not read the map — HTTP 404`. zerotal.dev did exactly this.
|
|
21
|
+
|
|
22
|
+
`start()` now probes `api/channels` and mounts only if it answers. Nothing is constructed
|
|
23
|
+
before that resolves — no shell, no shadow root, no `EventSource`, no listeners. Any
|
|
24
|
+
failure (404, offline, CSP, a proxy answering HTML) is read as absent: a missed panel costs
|
|
25
|
+
a developer one keystroke, and a stray one is a debug surface on a production page.
|
|
26
|
+
|
|
11
27
|
## [1.7.1] — 2026-08-16
|
|
12
28
|
|
|
13
29
|
### Changed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zerotal/devtools",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"maturity": "stable",
|
|
6
6
|
"private": false,
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
"typecheck": "tsc --noEmit"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@zerotal/core": "1.7.
|
|
34
|
+
"@zerotal/core": "1.7.4"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"typescript": "^5.8.0",
|
|
38
|
-
"@zerotal/orm": "1.7.
|
|
38
|
+
"@zerotal/orm": "1.7.4"
|
|
39
39
|
},
|
|
40
40
|
"description": "In-browser developer tools for Zerotal — request traces, an inspector panel, and an extensible tab registry.",
|
|
41
41
|
"keywords": [
|
package/src/client/index.ts
CHANGED
|
@@ -78,28 +78,74 @@ export const DevTools = {
|
|
|
78
78
|
const base = (opts.endpoint ?? "/__zerotal/devtools").replace(/\/$/, "");
|
|
79
79
|
const standalone = opts.mode === "standalone";
|
|
80
80
|
|
|
81
|
-
const
|
|
82
|
-
|
|
81
|
+
const mount = (): void => {
|
|
82
|
+
const store = new Store(standalone, base);
|
|
83
|
+
const transport = connect(base, store);
|
|
83
84
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
// What the browser measured for this page load, read once after it settles.
|
|
86
|
+
// The panel reports server duration as though it were the user's experience;
|
|
87
|
+
// it is not, and this is the only place that knows the difference.
|
|
88
|
+
onceLoaded(() => {
|
|
89
|
+
store.clientMetrics = collectClientMetrics();
|
|
90
|
+
store.changed();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
mountShell({
|
|
94
|
+
base,
|
|
95
|
+
standalone,
|
|
96
|
+
mount: opts.mount ?? document.body,
|
|
97
|
+
store,
|
|
98
|
+
transport,
|
|
99
|
+
tabs: BUILT_IN,
|
|
100
|
+
});
|
|
101
|
+
};
|
|
91
102
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
103
|
+
// Mount nothing until the server half answers.
|
|
104
|
+
//
|
|
105
|
+
// The provider is gated on the environment, so in production the endpoints are
|
|
106
|
+
// absent — and the client took that to mean it could start anyway and simply
|
|
107
|
+
// connect to nothing. It could not: `mountShell` pinned the panel to the page
|
|
108
|
+
// regardless, so zerotal.dev served a floating DevTools bar to every visitor,
|
|
109
|
+
// opening onto tabs reading `Could not read the map — HTTP 404` because the
|
|
110
|
+
// routes behind them do not exist in production.
|
|
111
|
+
//
|
|
112
|
+
// Failing closed here rather than in each app's entry file is deliberate: an
|
|
113
|
+
// app that calls `start()` unconditionally — which the docs site did, with a
|
|
114
|
+
// comment explaining why that was safe — is covered without knowing to be.
|
|
115
|
+
void serverPresent(base).then((present) => {
|
|
116
|
+
if (!present) return;
|
|
117
|
+
if (document.getElementById("__zerotal_dt__")) return;
|
|
118
|
+
mount();
|
|
99
119
|
});
|
|
100
120
|
},
|
|
101
121
|
};
|
|
102
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Whether the devtools routes exist on this origin.
|
|
125
|
+
*
|
|
126
|
+
* `api/channels` rather than `sse`: it answers and closes, where the stream stays
|
|
127
|
+
* open and would leave a connection hanging on every page load just to discover
|
|
128
|
+
* the panel should not be there. `no-store` so a 404 is not cached into a session
|
|
129
|
+
* that later starts a dev server on the same origin.
|
|
130
|
+
*
|
|
131
|
+
* Any failure — offline, blocked by CSP, a proxy returning HTML — is treated as
|
|
132
|
+
* absent. The panel is a development convenience, and the cost of guessing wrong
|
|
133
|
+
* is that a developer presses Alt+D twice; the cost of guessing wrong the other
|
|
134
|
+
* way is a debug surface on a production page.
|
|
135
|
+
*/
|
|
136
|
+
async function serverPresent(base: string): Promise<boolean> {
|
|
137
|
+
try {
|
|
138
|
+
const res = await fetch(`${base}/api/channels`, {
|
|
139
|
+
method: "GET",
|
|
140
|
+
cache: "no-store",
|
|
141
|
+
headers: { accept: "application/json" },
|
|
142
|
+
});
|
|
143
|
+
return res.ok;
|
|
144
|
+
} catch {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
103
149
|
// ── Public surface ────────────────────────────────────────────────────────────
|
|
104
150
|
//
|
|
105
151
|
// The panel is markup, and markup is awkward to assert on. What is exported here
|