@octanejs/devtools 0.0.36 → 0.0.37
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 +4 -4
- package/src/bridge.ts +25 -2
- package/src/client.ts +39 -0
- package/src/panel/ComponentsTab.tsrx +101 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octanejs/devtools",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.37",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -42,14 +42,14 @@
|
|
|
42
42
|
"@tanstack/devtools-event-client": "0.5.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"octane": "0.1.
|
|
45
|
+
"octane": "0.1.49"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@tanstack/devtools-event-bus": "0.4.2",
|
|
49
49
|
"@testing-library/jest-dom": "^6.9.1",
|
|
50
50
|
"vitest": "^4.1.10",
|
|
51
|
-
"@octanejs/testing-library": "0.1.
|
|
52
|
-
"octane": "0.1.
|
|
51
|
+
"@octanejs/testing-library": "0.1.46",
|
|
52
|
+
"octane": "0.1.49"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"test": "vitest run"
|
package/src/bridge.ts
CHANGED
|
@@ -43,6 +43,7 @@ const DEVTOOLS_OWN_COMPONENTS = new Set([
|
|
|
43
43
|
'TransitionsTab',
|
|
44
44
|
'PerfModelTab',
|
|
45
45
|
'TreeRow',
|
|
46
|
+
'NativeReadRows',
|
|
46
47
|
'DevtoolsPortal',
|
|
47
48
|
'TanStackDevtools',
|
|
48
49
|
]);
|
|
@@ -114,6 +115,24 @@ export function startBridge(
|
|
|
114
115
|
let lastTree = '';
|
|
115
116
|
let lastProfile = '';
|
|
116
117
|
let lastTransition = '';
|
|
118
|
+
let selectedId: number | null = null;
|
|
119
|
+
let lastInspect = '';
|
|
120
|
+
|
|
121
|
+
const emitInspect = (force = false) => {
|
|
122
|
+
if (selectedId === null) return;
|
|
123
|
+
const detail = hook.inspect(selectedId);
|
|
124
|
+
if (detail === null) {
|
|
125
|
+
client.emit('inspect-clear', { id: selectedId });
|
|
126
|
+
selectedId = null;
|
|
127
|
+
lastInspect = '';
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const serialized = JSON.stringify(detail);
|
|
131
|
+
if (force || serialized !== lastInspect) {
|
|
132
|
+
lastInspect = serialized;
|
|
133
|
+
client.emit('inspect', detail);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
117
136
|
|
|
118
137
|
const emitNow = () => {
|
|
119
138
|
if (trailing !== null) {
|
|
@@ -144,6 +163,9 @@ export function startBridge(
|
|
|
144
163
|
client.emit('transition', transition);
|
|
145
164
|
}
|
|
146
165
|
}
|
|
166
|
+
// Detail remains lazy: refresh only the selected Scope, even when its
|
|
167
|
+
// native revisions change without changing the component tree shape.
|
|
168
|
+
emitInspect();
|
|
147
169
|
};
|
|
148
170
|
|
|
149
171
|
const schedule = () => {
|
|
@@ -156,8 +178,8 @@ export function startBridge(
|
|
|
156
178
|
const offFlush = hook.subscribe(schedule);
|
|
157
179
|
|
|
158
180
|
const offRequest = client.on('inspect-request', (e) => {
|
|
159
|
-
|
|
160
|
-
|
|
181
|
+
selectedId = e.payload.id;
|
|
182
|
+
emitInspect(true);
|
|
161
183
|
});
|
|
162
184
|
|
|
163
185
|
// The panel asks for a fresh snapshot when it mounts; force a full re-emit
|
|
@@ -167,6 +189,7 @@ export function startBridge(
|
|
|
167
189
|
lastTree = '';
|
|
168
190
|
lastProfile = '';
|
|
169
191
|
lastTransition = '';
|
|
192
|
+
lastInspect = '';
|
|
170
193
|
emitNow();
|
|
171
194
|
});
|
|
172
195
|
|
package/src/client.ts
CHANGED
|
@@ -22,6 +22,44 @@ export interface NodeDetail {
|
|
|
22
22
|
hooks: WireHookCell[];
|
|
23
23
|
context: Array<{ name: string; value: unknown }>;
|
|
24
24
|
effectCount: number;
|
|
25
|
+
/** Optional capability: older runtimes and scopes without native reads omit it. */
|
|
26
|
+
nativeReads?: NativeReadOwnerWire;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface NativeReadSourceWire {
|
|
30
|
+
scopeKey: string;
|
|
31
|
+
key: string;
|
|
32
|
+
read: 'value' | 'latest' | 'snapshot';
|
|
33
|
+
kind: 'signal' | 'derived' | 'async';
|
|
34
|
+
status: 'ready' | 'pending' | 'error' | 'unevaluated';
|
|
35
|
+
revision: number;
|
|
36
|
+
generation?: number;
|
|
37
|
+
epoch: number;
|
|
38
|
+
retired: boolean;
|
|
39
|
+
historical: boolean;
|
|
40
|
+
retained: boolean;
|
|
41
|
+
refreshing: boolean;
|
|
42
|
+
connection: 'none' | 'connecting' | 'open' | 'closed';
|
|
43
|
+
complete: boolean;
|
|
44
|
+
dependencies: readonly { scopeKey: string; key: string }[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface NativeReadWire {
|
|
48
|
+
observedVersion: number | null;
|
|
49
|
+
currentVersion: number;
|
|
50
|
+
source: NativeReadSourceWire | null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface NativeReadAttemptWire {
|
|
54
|
+
mixed: boolean;
|
|
55
|
+
reads: readonly NativeReadWire[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface NativeReadOwnerWire {
|
|
59
|
+
ownerId: number;
|
|
60
|
+
committed: NativeReadAttemptWire | null;
|
|
61
|
+
pending: readonly NativeReadAttemptWire[];
|
|
62
|
+
retry: readonly NativeReadWire[];
|
|
25
63
|
}
|
|
26
64
|
export interface InspectRequest {
|
|
27
65
|
id: number;
|
|
@@ -70,6 +108,7 @@ type OctaneDevtoolsEventMap = {
|
|
|
70
108
|
tree: TreeSnapshot;
|
|
71
109
|
inspect: NodeDetail;
|
|
72
110
|
'inspect-request': InspectRequest;
|
|
111
|
+
'inspect-clear': InspectRequest;
|
|
73
112
|
profile: ProfileSnapshot;
|
|
74
113
|
transition: TransitionSnapshot;
|
|
75
114
|
// Panel → bridge: "send me a fresh snapshot now" (emitted when the panel
|
|
@@ -4,7 +4,71 @@
|
|
|
4
4
|
// (`inspect-request`); it never mutates app state (see docs/devtools.md P1 scope).
|
|
5
5
|
import { useEffect, useState } from 'octane';
|
|
6
6
|
import type { OctaneNode } from 'octane';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
OctaneDevtoolsEventClient,
|
|
9
|
+
type NativeReadWire,
|
|
10
|
+
type NodeDetail,
|
|
11
|
+
type WireTreeNode,
|
|
12
|
+
} from '../client';
|
|
13
|
+
|
|
14
|
+
function NativeReadRows(props: { label: string; reads: readonly NativeReadWire[] }) @{
|
|
15
|
+
<div>
|
|
16
|
+
<div class="odt-h">{props.label}</div>
|
|
17
|
+
@if (props.reads.length === 0) {
|
|
18
|
+
<div class="odt-empty odt-muted">No native reads.</div>
|
|
19
|
+
} @else {
|
|
20
|
+
@for (const read of props.reads; index i) {
|
|
21
|
+
<div class="odt-kv">
|
|
22
|
+
@if (read.source) {
|
|
23
|
+
<div>
|
|
24
|
+
<div class="odt-kv-key">{read.source.scopeKey + ':' + read.source.key}</div>
|
|
25
|
+
<div class="odt-muted">
|
|
26
|
+
{read.source.kind + ' · ' + read.source.read + ' · ' + read.source.status}
|
|
27
|
+
</div>
|
|
28
|
+
@if (read.source.retired) {
|
|
29
|
+
<span class="odt-badge">retired</span>
|
|
30
|
+
}
|
|
31
|
+
@if (read.source.historical) {
|
|
32
|
+
<span class="odt-badge">historical</span>
|
|
33
|
+
}
|
|
34
|
+
@if (read.source.retained) {
|
|
35
|
+
<span class="odt-badge">retained value</span>
|
|
36
|
+
}
|
|
37
|
+
@if (read.source.refreshing) {
|
|
38
|
+
<span class="odt-badge">refreshing</span>
|
|
39
|
+
}
|
|
40
|
+
@if (read.source.connection !== 'none') {
|
|
41
|
+
<span class="odt-badge">{'stream: ' + read.source.connection}</span>
|
|
42
|
+
}
|
|
43
|
+
@if (read.source.complete) {
|
|
44
|
+
<span class="odt-badge">complete</span>
|
|
45
|
+
}
|
|
46
|
+
<div class="odt-muted">
|
|
47
|
+
{'Node revision ' + read.source.revision + ', scope epoch ' + read.source.epoch}
|
|
48
|
+
</div>
|
|
49
|
+
@if (read.source.dependencies.length > 0) {
|
|
50
|
+
<div class="odt-muted">
|
|
51
|
+
{'Dependencies: ' +
|
|
52
|
+
read.source.dependencies.map(
|
|
53
|
+
(dependency) => dependency.scopeKey + ':' + dependency.key,
|
|
54
|
+
).join(', ')}
|
|
55
|
+
</div>
|
|
56
|
+
}
|
|
57
|
+
</div>
|
|
58
|
+
} @else {
|
|
59
|
+
<span class="odt-kv-key">Native source</span>
|
|
60
|
+
}
|
|
61
|
+
<span class="odt-kv-val">
|
|
62
|
+
{'Read revision ' +
|
|
63
|
+
(read.observedVersion === null ? 'uncommitted' : read.observedVersion) +
|
|
64
|
+
' → ' +
|
|
65
|
+
read.currentVersion}
|
|
66
|
+
</span>
|
|
67
|
+
</div>
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
</div>
|
|
71
|
+
}
|
|
8
72
|
|
|
9
73
|
function TreeRow(props: {
|
|
10
74
|
node: WireTreeNode;
|
|
@@ -34,20 +98,30 @@ function TreeRow(props: {
|
|
|
34
98
|
export function ComponentsTab(props: { client: OctaneDevtoolsEventClient }): OctaneNode {
|
|
35
99
|
const { client } = props;
|
|
36
100
|
const [nodes, setNodes] = useState<WireTreeNode[]>([]);
|
|
37
|
-
const [selected, setSelected] = useState<number | null>(null);
|
|
101
|
+
const [selected, setSelected, getSelected] = useState<number | null>(null);
|
|
38
102
|
const [detail, setDetail] = useState<NodeDetail | null>(null);
|
|
39
103
|
|
|
40
104
|
useEffect(() => {
|
|
41
105
|
const offTree = client.on('tree', (e) => setNodes(e.payload.nodes));
|
|
42
|
-
const offInspect = client.on('inspect', (e) =>
|
|
106
|
+
const offInspect = client.on('inspect', (e) => {
|
|
107
|
+
if (e.payload.id === getSelected()) setDetail(e.payload);
|
|
108
|
+
});
|
|
109
|
+
const offClear = client.on('inspect-clear', (e) => {
|
|
110
|
+
if (e.payload.id === getSelected()) {
|
|
111
|
+
setDetail(null);
|
|
112
|
+
setSelected(null);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
43
115
|
return () => {
|
|
44
116
|
offTree();
|
|
45
117
|
offInspect();
|
|
118
|
+
offClear();
|
|
46
119
|
};
|
|
47
120
|
});
|
|
48
121
|
|
|
49
122
|
const onSelect = (id: number) => {
|
|
50
123
|
setSelected(id);
|
|
124
|
+
setDetail(null);
|
|
51
125
|
client.emit('inspect-request', { id });
|
|
52
126
|
};
|
|
53
127
|
|
|
@@ -91,6 +165,30 @@ export function ComponentsTab(props: { client: OctaneDevtoolsEventClient }): Oct
|
|
|
91
165
|
}
|
|
92
166
|
</>
|
|
93
167
|
}
|
|
168
|
+
@if (detail.nativeReads) {
|
|
169
|
+
<div>
|
|
170
|
+
<div class="odt-h">Native reads</div>
|
|
171
|
+
<div class="odt-muted">{'Scheduled owner #' + detail.nativeReads.ownerId}</div>
|
|
172
|
+
<NativeReadRows
|
|
173
|
+
label="Accepted reads"
|
|
174
|
+
reads={detail.nativeReads.committed?.reads ?? []}
|
|
175
|
+
/>
|
|
176
|
+
@if (detail.nativeReads.committed?.mixed) {
|
|
177
|
+
<div class="odt-muted">Mixed revisions in this attempt.</div>
|
|
178
|
+
}
|
|
179
|
+
@for (const attempt of detail.nativeReads.pending; index i) {
|
|
180
|
+
<div>
|
|
181
|
+
<NativeReadRows label="Pending attempt" reads={attempt.reads} />
|
|
182
|
+
@if (attempt.mixed) {
|
|
183
|
+
<div class="odt-muted">Mixed revisions in this attempt.</div>
|
|
184
|
+
}
|
|
185
|
+
</div>
|
|
186
|
+
}
|
|
187
|
+
@if (detail.nativeReads.retry.length > 0) {
|
|
188
|
+
<NativeReadRows label="Suspended retry sources" reads={detail.nativeReads.retry} />
|
|
189
|
+
}
|
|
190
|
+
</div>
|
|
191
|
+
}
|
|
94
192
|
</div>
|
|
95
193
|
} @else {
|
|
96
194
|
<div class="odt-empty">Select a component.</div>
|