@djangocfg/devtools 2.1.569 → 2.1.570
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/devtools",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.570",
|
|
4
4
|
"description": "Frontend devtools for django-cfg apps: error/console capture with backend ingest, plus a floating debug panel. One package, one event stream.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"django",
|
|
@@ -51,16 +51,16 @@
|
|
|
51
51
|
"test:watch": "vitest"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
|
-
"@djangocfg/ui-core": "^2.1.
|
|
54
|
+
"@djangocfg/ui-core": "^2.1.570",
|
|
55
55
|
"lucide-react": ">=0.400.0",
|
|
56
56
|
"react": "^19.0.0",
|
|
57
57
|
"react-dom": "^19.0.0",
|
|
58
58
|
"zustand": "^5.0.0"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@djangocfg/eslint-config": "^2.1.
|
|
62
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
63
|
-
"@djangocfg/ui-core": "^2.1.
|
|
61
|
+
"@djangocfg/eslint-config": "^2.1.570",
|
|
62
|
+
"@djangocfg/typescript-config": "^2.1.570",
|
|
63
|
+
"@djangocfg/ui-core": "^2.1.570",
|
|
64
64
|
"@storybook/react-vite": "^10.5.0",
|
|
65
65
|
"@types/node": "^25.9.5",
|
|
66
66
|
"@types/react": "19.2.15",
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { isStreamingRuntimeNoise } from '../capture';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* React's inline streaming scripts fail only when something outside the app
|
|
7
|
+
* removed the hidden segment first. The stacks below are verbatim from
|
|
8
|
+
* carapis.com's monitor log, 2026-09-23.
|
|
9
|
+
*/
|
|
10
|
+
describe('isStreamingRuntimeNoise', () => {
|
|
11
|
+
it('drops a $RS failure', () => {
|
|
12
|
+
const stack =
|
|
13
|
+
"TypeError: Cannot read properties of null (reading 'parentNode')\n" +
|
|
14
|
+
' at $RS (https://carapis.com/catalog/genesis/eq900/c2d25771/:2:3330)\n' +
|
|
15
|
+
' at https://carapis.com/catalog/genesis/eq900/c2d25771/:3:77605';
|
|
16
|
+
expect(isStreamingRuntimeNoise(stack)).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('drops a $RC failure', () => {
|
|
20
|
+
expect(
|
|
21
|
+
isStreamingRuntimeNoise("TypeError: x\n at $RC (https://a.test/:2:10)\n at https://a.test/:9:1"),
|
|
22
|
+
).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('keeps an app error whose top frame is the app', () => {
|
|
26
|
+
const stack =
|
|
27
|
+
"TypeError: Cannot read properties of null (reading 'parentNode')\n" +
|
|
28
|
+
' at Gallery (https://carapis.com/_next/static/chunks/abc.js:1:200)\n' +
|
|
29
|
+
' at $RS (https://carapis.com/x/:2:3330)';
|
|
30
|
+
expect(isStreamingRuntimeNoise(stack)).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('keeps an app function that merely starts with $R', () => {
|
|
34
|
+
expect(isStreamingRuntimeNoise('Error\n at $Router (https://a.test/app.js:1:1)')).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('keeps an event with no stack', () => {
|
|
38
|
+
expect(isStreamingRuntimeNoise(undefined)).toBe(false);
|
|
39
|
+
expect(isStreamingRuntimeNoise('')).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
});
|
package/src/capture.ts
CHANGED
|
@@ -31,9 +31,31 @@ const LIBRARY_NOISE: RegExp[] = [
|
|
|
31
31
|
/input-otp could not insert CSS rule/i,
|
|
32
32
|
]
|
|
33
33
|
|
|
34
|
+
// React's streaming-SSR runtime: the inline `$RS` (reveal segment), `$RC`
|
|
35
|
+
// (complete boundary) and kin that the server writes into the HTML to move
|
|
36
|
+
// each late chunk from its `<div hidden id="S:n">` into its placeholder.
|
|
37
|
+
// They throw only when that hidden element is already gone — something
|
|
38
|
+
// outside the app rewrote the document before the stream finished (a
|
|
39
|
+
// headless scraper stripping hidden nodes, a reader-mode or translation
|
|
40
|
+
// rewrite). The frame is React's, never the app's, and nothing on the page
|
|
41
|
+
// can fix it. Measured on carapis.com, 2026-09-23: 19,381 events in a day,
|
|
42
|
+
// 99.9% from one fixed "Chrome/145" user agent, each page load failing all
|
|
43
|
+
// 19 of its `$RS` calls at once — while a real browser on the same URL
|
|
44
|
+
// raised none.
|
|
45
|
+
const STREAMING_RUNTIME_FRAME = /^\s*at \$R[A-Z]{1,2} \(/m
|
|
46
|
+
|
|
47
|
+
export function isStreamingRuntimeNoise(stack?: string): boolean {
|
|
48
|
+
if (!stack) return false
|
|
49
|
+
// The TOP frame only: an app error that merely passed through a streamed
|
|
50
|
+
// chunk still has its own frame first, and must still be reported.
|
|
51
|
+
const firstFrame = stack.split('\n').find((line) => /^\s*at /.test(line))
|
|
52
|
+
return firstFrame !== undefined && STREAMING_RUNTIME_FRAME.test(firstFrame)
|
|
53
|
+
}
|
|
54
|
+
|
|
34
55
|
function isNoise(msg: string, stack?: string): boolean {
|
|
35
56
|
if (HYDRATION_NOISE.some((p) => p.test(msg))) return true
|
|
36
57
|
if (LIBRARY_NOISE.some((p) => p.test(msg))) return true
|
|
58
|
+
if (isStreamingRuntimeNoise(stack)) return true
|
|
37
59
|
// Our own transport failing must never loop back into capture.
|
|
38
60
|
return INGEST_PATTERN.test(msg) || INGEST_PATTERN.test(stack ?? '')
|
|
39
61
|
}
|