@proteus-vue/devtools 0.1.0

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/dist/index.js ADDED
@@ -0,0 +1,3323 @@
1
+ // src/panel.ts
2
+ import {
3
+ createTimelineCollector,
4
+ createFlamegraphCollector,
5
+ createErrorDiagnoser
6
+ } from "@proteus-vue/devtools-runtime";
7
+
8
+ // src/tooltip.ts
9
+ var TIP_DATA = /* @__PURE__ */ Symbol("pdTip");
10
+ function attachTip(el, data) {
11
+ el.dataset.tip = "";
12
+ el[TIP_DATA] = data;
13
+ }
14
+ function resolveTipData(target) {
15
+ return target[TIP_DATA] ?? null;
16
+ }
17
+ function createTooltipLayer() {
18
+ const tip = document.createElement("div");
19
+ tip.className = "pd-tooltip";
20
+ tip.style.display = "none";
21
+ tip.style.position = "fixed";
22
+ tip.style.zIndex = "1000";
23
+ tip.style.pointerEvents = "none";
24
+ document.body.appendChild(tip);
25
+ let visible = false;
26
+ let hideTimer = null;
27
+ return {
28
+ show(data, x, y) {
29
+ if (hideTimer) {
30
+ clearTimeout(hideTimer);
31
+ hideTimer = null;
32
+ }
33
+ tip.replaceChildren();
34
+ const title = document.createElement("div");
35
+ title.className = "pd-tooltip-title";
36
+ title.textContent = data.title;
37
+ tip.appendChild(title);
38
+ for (const line of data.lines) {
39
+ const row = document.createElement("div");
40
+ row.className = "pd-tooltip-line";
41
+ row.textContent = line;
42
+ tip.appendChild(row);
43
+ }
44
+ tip.style.display = "block";
45
+ const rect = tip.getBoundingClientRect();
46
+ let left = x + 12;
47
+ let top = y + 12;
48
+ if (left + rect.width > window.innerWidth) left = x - rect.width - 12;
49
+ if (top + rect.height > window.innerHeight) top = y - rect.height - 12;
50
+ tip.style.left = left + "px";
51
+ tip.style.top = top + "px";
52
+ visible = true;
53
+ },
54
+ hide() {
55
+ if (hideTimer) clearTimeout(hideTimer);
56
+ tip.style.display = "none";
57
+ visible = false;
58
+ },
59
+ dispose() {
60
+ if (hideTimer) clearTimeout(hideTimer);
61
+ tip.remove();
62
+ visible = false;
63
+ },
64
+ get visible() {
65
+ return visible;
66
+ }
67
+ };
68
+ }
69
+ function bindTooltip(root, layer, resolve) {
70
+ let hoverTimer = null;
71
+ let current = null;
72
+ function onOver(e) {
73
+ const target = e.target.closest("[data-tip]");
74
+ if (!target) {
75
+ layer.hide();
76
+ return;
77
+ }
78
+ current = target;
79
+ if (hoverTimer) clearTimeout(hoverTimer);
80
+ hoverTimer = setTimeout(() => {
81
+ if (current !== target) return;
82
+ const data = resolve(target);
83
+ if (data) layer.show(data, e.clientX, e.clientY);
84
+ }, 150);
85
+ }
86
+ function onMove(e) {
87
+ if (!current || !layer.visible) return;
88
+ layer.show(resolve(current) ?? { title: "", lines: [] }, e.clientX, e.clientY);
89
+ }
90
+ function onOut() {
91
+ current = null;
92
+ if (hoverTimer) clearTimeout(hoverTimer);
93
+ layer.hide();
94
+ }
95
+ root.addEventListener("mouseover", onOver);
96
+ root.addEventListener("mousemove", onMove);
97
+ root.addEventListener("mouseout", onOut);
98
+ return () => {
99
+ root.removeEventListener("mouseover", onOver);
100
+ root.removeEventListener("mousemove", onMove);
101
+ root.removeEventListener("mouseout", onOut);
102
+ layer.hide();
103
+ };
104
+ }
105
+
106
+ // src/views/timeline.ts
107
+ var LANE_H = 26;
108
+ var OVERSCAN = 2;
109
+ function renderTimeline(container, data) {
110
+ container.replaceChildren();
111
+ const spans = data.spans;
112
+ if (spans.length === 0) {
113
+ const empty = document.createElement("div");
114
+ empty.className = "pd-empty";
115
+ empty.textContent = "\u6682\u65E0\u4E8B\u4EF6\uFF08TraceBus \u672A\u4E0A\u62A5\uFF09";
116
+ container.appendChild(empty);
117
+ return;
118
+ }
119
+ let winStart = data.window?.start ?? Infinity;
120
+ let winEnd = data.window?.end ?? -Infinity;
121
+ if (!data.window) {
122
+ for (const s of spans) {
123
+ if (s.start < winStart) winStart = s.start;
124
+ const end = s.end !== void 0 ? s.end : s.start;
125
+ if (end > winEnd) winEnd = end;
126
+ }
127
+ }
128
+ const total = Math.max(1, winEnd - winStart);
129
+ const visible = data.window ? spans.filter((s) => {
130
+ const end = s.end !== void 0 ? s.end : s.start;
131
+ return end >= winStart && s.start <= winEnd;
132
+ }) : spans;
133
+ const ruler = document.createElement("div");
134
+ ruler.className = "pd-ruler";
135
+ for (let i = 0; i <= 4; i++) {
136
+ const mark = document.createElement("span");
137
+ mark.style.width = "25%";
138
+ mark.textContent = Math.round(winStart + total * i / 4) + "ms";
139
+ ruler.appendChild(mark);
140
+ }
141
+ container.appendChild(ruler);
142
+ const lanes = /* @__PURE__ */ new Map();
143
+ for (const s of visible) {
144
+ const list = lanes.get(s.source);
145
+ if (list) list.push(s);
146
+ else lanes.set(s.source, [s]);
147
+ }
148
+ const laneList = Array.from(lanes);
149
+ if (data.virtual) {
150
+ const scrollTop = Math.max(0, data.virtual.scrollTop);
151
+ const viewHeight = Math.max(1, data.virtual.viewHeight);
152
+ container.style.overflowY = "auto";
153
+ const spacer = document.createElement("div");
154
+ spacer.className = "pd-timeline-spacer";
155
+ spacer.style.height = laneList.length * LANE_H + "px";
156
+ const startIdx = Math.max(0, Math.floor((scrollTop - OVERSCAN * LANE_H) / LANE_H));
157
+ const endIdx = Math.min(laneList.length, Math.ceil((scrollTop + viewHeight + OVERSCAN * LANE_H) / LANE_H));
158
+ for (let i = startIdx; i < endIdx; i++) {
159
+ const entry = laneList[i];
160
+ const lane = buildLane(entry[0], entry[1], winStart, total);
161
+ lane.style.position = "absolute";
162
+ lane.style.top = i * LANE_H + "px";
163
+ lane.style.left = "0";
164
+ lane.style.right = "0";
165
+ lane.style.height = LANE_H + "px";
166
+ lane.style.margin = "0";
167
+ spacer.appendChild(lane);
168
+ }
169
+ container.appendChild(spacer);
170
+ return;
171
+ }
172
+ for (const entry of laneList) {
173
+ container.appendChild(buildLane(entry[0], entry[1], winStart, total));
174
+ }
175
+ }
176
+ function buildLane(source, laneSpans, winStart, total) {
177
+ const lane = document.createElement("div");
178
+ lane.className = "pd-lane";
179
+ const label = document.createElement("div");
180
+ label.className = "pd-lane-label";
181
+ label.textContent = source;
182
+ lane.appendChild(label);
183
+ const track = document.createElement("div");
184
+ track.className = "pd-lane-track";
185
+ for (const s of laneSpans) {
186
+ const seg = document.createElement("div");
187
+ const left = (s.start - winStart) / total * 100;
188
+ const end = s.end !== void 0 ? s.end : s.start;
189
+ const width = Math.max(0.5, (end - s.start) / total * 100);
190
+ seg.className = "pd-span" + (s.pending ? " pd-span-pending" : "") + (s.durationMs === 0 ? " pd-span-dot" : "");
191
+ seg.style.left = left.toFixed(2) + "%";
192
+ seg.style.width = width.toFixed(2) + "%";
193
+ attachTip(seg, {
194
+ title: `${s.source}.${s.name}`,
195
+ lines: [s.durationMs !== void 0 ? `\u8017\u65F6 ${s.durationMs}ms` : "\u77AC\u65F6\u4E8B\u4EF6", s.pending ? "\u9636\u6BB5: pending\uFF08\u8FDB\u884C\u4E2D\uFF09" : "\u9636\u6BB5: completed"]
196
+ });
197
+ const text = document.createElement("span");
198
+ text.textContent = s.name + (s.durationMs !== void 0 ? " " + s.durationMs + "ms" : "");
199
+ seg.appendChild(text);
200
+ track.appendChild(seg);
201
+ }
202
+ lane.appendChild(track);
203
+ return lane;
204
+ }
205
+
206
+ // src/views/timeline-interaction.ts
207
+ var ZOOM_STEP = 1.2;
208
+ var MIN_SPAN = 1;
209
+ function fullWindow(spans) {
210
+ let start = Infinity;
211
+ let end = -Infinity;
212
+ for (const s of spans) {
213
+ if (s.start < start) start = s.start;
214
+ const e = s.end !== void 0 ? s.end : s.start;
215
+ if (e > end) end = e;
216
+ }
217
+ return { start, end };
218
+ }
219
+ function createTimelineZoom(container, getSpans, opts = {}) {
220
+ const { onWindowChange } = opts;
221
+ let window2 = null;
222
+ let dragging = false;
223
+ let dragStartX = 0;
224
+ let dragStartWindow = null;
225
+ function apply(w) {
226
+ window2 = w;
227
+ onWindowChange?.(w);
228
+ }
229
+ function clamp(w) {
230
+ const full = fullWindow(getSpans());
231
+ const maxSpan = Math.max(1, full.end - full.start);
232
+ const span = Math.min(maxSpan, Math.max(MIN_SPAN, w.end - w.start));
233
+ let start = w.start;
234
+ if (start < full.start) start = full.start;
235
+ if (start + span > full.end) start = Math.max(full.start, full.end - span);
236
+ return { start, end: start + span };
237
+ }
238
+ function onWheel(e) {
239
+ e.preventDefault();
240
+ if (!window2) window2 = fullWindow(getSpans());
241
+ const span = window2.end - window2.start;
242
+ if (span <= 0) return;
243
+ const rect = container.getBoundingClientRect();
244
+ const width = Math.max(1, rect.width);
245
+ const cx = Number.isFinite(e.clientX) ? e.clientX : rect.left + width / 2;
246
+ const f = Math.min(1, Math.max(0, (cx - rect.left) / width));
247
+ const cursorTime = window2.start + f * span;
248
+ const factor = e.deltaY < 0 ? 1 / ZOOM_STEP : ZOOM_STEP;
249
+ const newSpan = span * factor;
250
+ const start = cursorTime - f * newSpan;
251
+ apply(clamp({ start, end: start + newSpan }));
252
+ }
253
+ function onDown(e) {
254
+ if (e.button !== 0) return;
255
+ if (!window2) window2 = fullWindow(getSpans());
256
+ dragging = true;
257
+ dragStartX = e.clientX;
258
+ dragStartWindow = { ...window2 };
259
+ e.preventDefault();
260
+ }
261
+ function onMove(e) {
262
+ if (!dragging || !dragStartWindow) return;
263
+ const span = dragStartWindow.end - dragStartWindow.start;
264
+ if (span <= 0) return;
265
+ const rect = container.getBoundingClientRect();
266
+ const dt = (e.clientX - dragStartX) / Math.max(1, rect.width) * span;
267
+ apply(clamp({ start: dragStartWindow.start - dt, end: dragStartWindow.end - dt }));
268
+ }
269
+ function onUp() {
270
+ dragging = false;
271
+ dragStartWindow = null;
272
+ }
273
+ function onDblClick() {
274
+ apply(fullWindow(getSpans()));
275
+ }
276
+ container.addEventListener("wheel", onWheel, { passive: false });
277
+ container.addEventListener("mousedown", onDown);
278
+ container.addEventListener("mousemove", onMove);
279
+ container.addEventListener("mouseup", onUp);
280
+ container.addEventListener("dblclick", onDblClick);
281
+ return {
282
+ getWindow: () => window2,
283
+ destroy() {
284
+ container.removeEventListener("wheel", onWheel);
285
+ container.removeEventListener("mousedown", onDown);
286
+ container.removeEventListener("mousemove", onMove);
287
+ container.removeEventListener("mouseup", onUp);
288
+ container.removeEventListener("dblclick", onDblClick);
289
+ dragging = false;
290
+ dragStartWindow = null;
291
+ window2 = null;
292
+ }
293
+ };
294
+ }
295
+
296
+ // src/views/flamegraph.ts
297
+ var ROW_HEIGHT = 22;
298
+ var SRC_COLORS = {
299
+ lifecycle: "#3e5770",
300
+ router: "#2e7d5b",
301
+ api: "#1d6fb8",
302
+ store: "#8a5a17",
303
+ compiler: "#6a3d9a",
304
+ capability: "#0e7c86",
305
+ component: "#9a3d52",
306
+ hmr: "#3d9a6a",
307
+ // ★合成「录制会话」根(视图层包装,不落数据/对比):中性灰
308
+ session: "#6b7280"
309
+ };
310
+ function srcColor(source) {
311
+ return SRC_COLORS[source] ?? "#555";
312
+ }
313
+ function pct(v) {
314
+ return Math.min(100, v).toFixed(2) + "%";
315
+ }
316
+ function clampPct(v) {
317
+ return Math.max(0.4, Math.min(100, v)).toFixed(2) + "%";
318
+ }
319
+ function compareMap(compare) {
320
+ const m = /* @__PURE__ */ new Map();
321
+ for (const c of compare) m.set(c.source + "\0" + c.name, c);
322
+ return m;
323
+ }
324
+ function renderFlamegraph(container, data, hooks = {}) {
325
+ container.replaceChildren();
326
+ const roots = data.focus ? [data.focus] : data.nodes;
327
+ if (roots.length === 0) {
328
+ const empty = document.createElement("div");
329
+ empty.className = "pd-empty";
330
+ empty.textContent = '\u6682\u65E0\u5F55\u5236\uFF08\u9762\u677F\u53F3\u4E0A\u89D2"\u5F00\u59CB\u5F55\u5236"\u540E\u4EA7\u751F\uFF09';
331
+ container.appendChild(empty);
332
+ return;
333
+ }
334
+ const cmp = data.compare ? compareMap(data.compare) : null;
335
+ const regs = data.compare ? data.compare.filter((c) => c.verdict === "regression") : [];
336
+ const imps = data.compare ? data.compare.filter((c) => c.verdict === "improvement") : [];
337
+ if (data.compare && data.compare.length > 0) {
338
+ const summary = document.createElement("div");
339
+ summary.className = "pd-cmp";
340
+ const head = document.createElement("div");
341
+ head.className = "pd-cmp-head";
342
+ head.textContent = `\u5BF9\u6BD4\u4E0A\u6B21\u5F55\u5236\uFF1A${regs.length} \u5904\u56DE\u5F52 \xB7 ${imps.length} \u5904\u4F18\u5316`;
343
+ summary.appendChild(head);
344
+ const sorted = data.compare.slice().sort((a, b) => Math.abs(b.deltaPct) - Math.abs(a.deltaPct)).slice(0, 8);
345
+ for (const c of sorted) {
346
+ const row = document.createElement("div");
347
+ row.className = "pd-cmp-row pd-cmp-" + c.verdict;
348
+ const name = document.createElement("span");
349
+ name.className = "pd-cmp-name";
350
+ name.textContent = c.source + "." + c.name;
351
+ const delta = document.createElement("span");
352
+ delta.className = "pd-cmp-delta";
353
+ delta.textContent = (c.deltaPct > 0 ? "+" : "") + c.deltaPct + "% (" + c.aMs + "\u2192" + c.bMs + "ms)";
354
+ row.appendChild(name);
355
+ row.appendChild(delta);
356
+ attachTip(row, {
357
+ title: `${c.source}.${c.name}`,
358
+ lines: [`\u4E0A\u6B21 ${c.aMs}ms \u2192 \u672C\u6B21 ${c.bMs}ms`, `\u53D8\u5316 ${(c.deltaPct > 0 ? "+" : "") + c.deltaPct}%`]
359
+ });
360
+ summary.appendChild(row);
361
+ }
362
+ container.appendChild(summary);
363
+ }
364
+ if (data.breadcrumb && data.breadcrumb.length > 0) {
365
+ const crumb = document.createElement("div");
366
+ crumb.className = "pd-fg-crumb";
367
+ for (const c of data.breadcrumb) {
368
+ const item = document.createElement("button");
369
+ item.className = "pd-fg-crumb-item";
370
+ item.textContent = c.name;
371
+ item.addEventListener("click", () => hooks.onFocus?.(c.id));
372
+ crumb.appendChild(item);
373
+ }
374
+ const up = document.createElement("button");
375
+ up.className = "pd-btn pd-fg-up";
376
+ up.textContent = "\u8FD4\u56DE\u4E0A\u7EA7";
377
+ up.addEventListener("click", () => hooks.onFocusUp?.());
378
+ crumb.appendChild(up);
379
+ container.appendChild(crumb);
380
+ }
381
+ let winStart = Infinity;
382
+ let winEnd = -Infinity;
383
+ for (const n of roots) {
384
+ if (n.startMs < winStart) winStart = n.startMs;
385
+ const end = n.startMs + n.durationMs;
386
+ if (end > winEnd) winEnd = end;
387
+ }
388
+ const total = Math.max(1, winEnd - winStart);
389
+ let renderRoots = roots;
390
+ if (!data.focus && roots.length >= 2) {
391
+ let childTotal = 0;
392
+ for (const r of roots) childTotal += r.durationMs;
393
+ renderRoots = [
394
+ {
395
+ id: "__session__",
396
+ source: "session",
397
+ name: "\u5F55\u5236\u4F1A\u8BDD",
398
+ startMs: winStart,
399
+ durationMs: total,
400
+ selfMs: Math.max(0, total - childTotal),
401
+ children: roots,
402
+ depth: 0
403
+ }
404
+ ];
405
+ }
406
+ const board = document.createElement("div");
407
+ board.className = "pd-fg-board";
408
+ let maxDepth = 0;
409
+ const depthOf = (n, d) => {
410
+ let m = d;
411
+ for (const c of n.children) m = Math.max(m, depthOf(c, d + 1));
412
+ return m;
413
+ };
414
+ for (const n of renderRoots) maxDepth = Math.max(maxDepth, depthOf(n, 0));
415
+ board.style.height = (maxDepth + 1) * ROW_HEIGHT + "px";
416
+ function renderNode(box, node, depth, relStart, relDur, isRoot) {
417
+ const leftPct = isRoot ? (node.startMs - winStart) / total * 100 : (node.startMs - relStart) / relDur * 100;
418
+ const widthPct = isRoot ? node.durationMs / total * 100 : node.durationMs / relDur * 100;
419
+ const block = document.createElement("div");
420
+ const isSession = node.id === "__session__";
421
+ block.className = "pd-fg-node" + (node.selfMs === 0 ? " pd-fg-zero" : "") + (isSession ? " pd-fg-session" : "");
422
+ block.style.background = srcColor(node.source);
423
+ const entry = cmp ? cmp.get(node.source + "\0" + node.name) : void 0;
424
+ if (entry && entry.verdict !== "same") block.classList.add(entry.verdict === "regression" ? "pd-fg-reg" : "pd-fg-imp");
425
+ block.style.left = pct(leftPct);
426
+ block.style.width = clampPct(widthPct);
427
+ block.style.top = "0px";
428
+ block.style.height = ROW_HEIGHT - 2 + "px";
429
+ const tipLines = [`inclusive ${node.durationMs}ms`, `exclusive ${node.selfMs}ms`];
430
+ if (entry && entry.verdict !== "same") tipLines.push(`\u5BF9\u6BD4 ${(entry.deltaPct > 0 ? "+" : "") + entry.deltaPct}%`);
431
+ attachTip(block, {
432
+ title: `${node.source}.${node.name}`,
433
+ lines: tipLines
434
+ });
435
+ const label = document.createElement("span");
436
+ label.textContent = node.name + " " + node.selfMs + "ms";
437
+ block.appendChild(label);
438
+ if (!isSession) block.addEventListener("click", () => hooks.onFocus?.(node.id));
439
+ box.appendChild(block);
440
+ let subDepth = 0;
441
+ if (node.children.length) {
442
+ const sub = document.createElement("div");
443
+ sub.className = "pd-fg-children";
444
+ sub.style.left = pct(leftPct);
445
+ sub.style.width = clampPct(widthPct);
446
+ sub.style.top = ROW_HEIGHT + "px";
447
+ let maxChildDepth = 0;
448
+ for (const c of node.children) maxChildDepth = Math.max(maxChildDepth, renderNode(sub, c, depth + 1, node.startMs, node.durationMs, false));
449
+ sub.style.height = maxChildDepth * ROW_HEIGHT + "px";
450
+ box.appendChild(sub);
451
+ subDepth = maxChildDepth;
452
+ }
453
+ return subDepth + 1;
454
+ }
455
+ for (const r of renderRoots) renderNode(board, r, 0, winStart, total, true);
456
+ container.appendChild(board);
457
+ }
458
+
459
+ // src/views/inspector.ts
460
+ function editInitial(value, kind) {
461
+ if (kind === "string") return String(value);
462
+ if (kind === "null") return value === void 0 ? "undefined" : "null";
463
+ return String(value);
464
+ }
465
+ function parseEdit(text, kind) {
466
+ if (kind === "number") {
467
+ const n = Number(text.trim());
468
+ if (!Number.isFinite(n)) return { ok: false };
469
+ return { ok: true, value: n };
470
+ }
471
+ if (kind === "boolean") {
472
+ if (text.trim() === "true") return { ok: true, value: true };
473
+ if (text.trim() === "false") return { ok: true, value: false };
474
+ return { ok: false };
475
+ }
476
+ if (kind === "string") return { ok: true, value: text };
477
+ const t = text.trim();
478
+ if (t === "" || t === "null") return { ok: true, value: null };
479
+ if (t === "undefined") return { ok: true, value: void 0 };
480
+ if (t === "true") return { ok: true, value: true };
481
+ if (t === "false") return { ok: true, value: false };
482
+ if (/^-?\d+(\.\d+)?$/.test(t)) return { ok: true, value: Number(t) };
483
+ return { ok: true, value: text };
484
+ }
485
+ function startEdit(valEl, value, kind, path, hooks) {
486
+ const input = document.createElement("input");
487
+ input.className = "pd-kv-edit";
488
+ input.value = editInitial(value, kind);
489
+ valEl.replaceWith(input);
490
+ input.focus();
491
+ let done = false;
492
+ const finish = (commit) => {
493
+ if (done) return;
494
+ done = true;
495
+ if (commit) {
496
+ const parsed = parseEdit(input.value, kind);
497
+ if (parsed.ok) {
498
+ const nk = kindOf(parsed.value);
499
+ valEl.textContent = formatPrimitive(parsed.value, nk);
500
+ valEl.className = "pd-kv-value pd-t-" + nk;
501
+ input.replaceWith(valEl);
502
+ hooks.onEdit?.(path, parsed.value);
503
+ } else {
504
+ input.replaceWith(valEl);
505
+ }
506
+ return;
507
+ }
508
+ input.replaceWith(valEl);
509
+ };
510
+ input.addEventListener("keydown", (e) => {
511
+ if (e.key === "Enter") finish(true);
512
+ else if (e.key === "Escape") finish(false);
513
+ });
514
+ input.addEventListener("blur", () => finish(true));
515
+ }
516
+ function kindOf(value) {
517
+ if (value === null || value === void 0) return "null";
518
+ const t = typeof value;
519
+ if (t === "number") return "number";
520
+ if (t === "string") return "string";
521
+ if (t === "boolean") return "boolean";
522
+ if (Array.isArray(value)) return "array";
523
+ return "object";
524
+ }
525
+ function summarize(value) {
526
+ const k = kindOf(value);
527
+ if (k === "array") return "Array(" + value.length + ")";
528
+ if (k === "object") {
529
+ const keys = Object.keys(value);
530
+ return "Object {" + keys.slice(0, 3).join(", ") + (keys.length > 3 ? ", \u2026" : "") + "}";
531
+ }
532
+ return formatPrimitive(value, k);
533
+ }
534
+ function formatPrimitive(value, kind) {
535
+ if (kind === "string") return JSON.stringify(value);
536
+ if (kind === "null") return value === void 0 ? "undefined" : "null";
537
+ return String(value);
538
+ }
539
+ function renderKeyValue(container, key, value, depth, initiallyOpen = false, path = [], hooks = {}) {
540
+ const kind = kindOf(value);
541
+ const row = document.createElement("div");
542
+ row.className = "pd-kv";
543
+ row.style.paddingLeft = 10 + depth * 14 + "px";
544
+ const toggle = document.createElement("span");
545
+ toggle.className = "pd-kv-toggle";
546
+ const keyEl = document.createElement("span");
547
+ keyEl.className = "pd-kv-key";
548
+ keyEl.textContent = key;
549
+ const valEl = document.createElement("span");
550
+ valEl.className = "pd-kv-value pd-t-" + kind;
551
+ const collapsible = kind === "object" || kind === "array";
552
+ toggle.textContent = collapsible ? "\u25B8" : "";
553
+ if (collapsible) {
554
+ valEl.textContent = summarize(value);
555
+ const childBox = document.createElement("div");
556
+ childBox.style.display = initiallyOpen ? "block" : "none";
557
+ if (initiallyOpen) toggle.textContent = "\u25BE";
558
+ let built = false;
559
+ const build = () => {
560
+ if (built) return;
561
+ built = true;
562
+ const entries = kind === "array" ? value.map((v, i) => [String(i), v]) : Object.entries(value);
563
+ for (const [k, v] of entries) renderKeyValue(childBox, k, v, depth + 1, false, path.concat(k), hooks);
564
+ };
565
+ if (initiallyOpen) build();
566
+ const expand = () => {
567
+ const open = childBox.style.display !== "none";
568
+ childBox.style.display = open ? "none" : "block";
569
+ toggle.textContent = open ? "\u25B8" : "\u25BE";
570
+ if (!open) build();
571
+ };
572
+ row.addEventListener("click", expand);
573
+ row.appendChild(toggle);
574
+ row.appendChild(keyEl);
575
+ row.appendChild(valEl);
576
+ container.appendChild(row);
577
+ container.appendChild(childBox);
578
+ return;
579
+ }
580
+ valEl.textContent = formatPrimitive(value, kind);
581
+ if (hooks.onEdit) {
582
+ valEl.classList.add("pd-kv-editable");
583
+ valEl.addEventListener("click", (e) => {
584
+ e.stopPropagation();
585
+ startEdit(valEl, value, kind, path, hooks);
586
+ });
587
+ }
588
+ row.appendChild(toggle);
589
+ row.appendChild(keyEl);
590
+ row.appendChild(valEl);
591
+ container.appendChild(row);
592
+ }
593
+
594
+ // src/views/state.ts
595
+ function diffLines(before, after) {
596
+ const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
597
+ const lines = [];
598
+ for (const k of keys) {
599
+ const b = before[k];
600
+ const a = after[k];
601
+ if (JSON.stringify(b) === JSON.stringify(a)) continue;
602
+ lines.push(k + ": " + summarize(b) + " \u2192 " + summarize(a));
603
+ }
604
+ return lines;
605
+ }
606
+ function renderState(container, data, hooks = {}) {
607
+ container.replaceChildren();
608
+ const stores = data.snapshot.stores;
609
+ const selected = data.selectedStore !== void 0 && stores.some((s) => s.id === data.selectedStore) ? data.selectedStore : stores[0]?.id ?? "";
610
+ const toolbar = document.createElement("div");
611
+ toolbar.className = "pd-toolbar";
612
+ const exportBtn = document.createElement("button");
613
+ exportBtn.className = "pd-btn";
614
+ exportBtn.textContent = "\u5BFC\u51FA\u5FEB\u7167 JSON";
615
+ exportBtn.addEventListener("click", () => hooks.onExport?.());
616
+ toolbar.appendChild(exportBtn);
617
+ const importBtn = document.createElement("button");
618
+ importBtn.className = "pd-btn";
619
+ importBtn.textContent = "\u5BFC\u5165\u5FEB\u7167 JSON";
620
+ const fileInput = document.createElement("input");
621
+ fileInput.type = "file";
622
+ fileInput.accept = ".json,application/json";
623
+ fileInput.style.display = "none";
624
+ importBtn.addEventListener("click", () => fileInput.click());
625
+ fileInput.addEventListener("change", () => {
626
+ const f = fileInput.files?.[0];
627
+ fileInput.value = "";
628
+ if (!f) return;
629
+ const reader = new FileReader();
630
+ reader.onload = () => hooks.onImport?.(String(reader.result ?? ""));
631
+ reader.readAsText(f);
632
+ });
633
+ toolbar.appendChild(importBtn);
634
+ toolbar.appendChild(fileInput);
635
+ if (hooks.onExportSession || hooks.onImportSession) {
636
+ const sessionExportBtn = document.createElement("button");
637
+ sessionExportBtn.className = "pd-btn";
638
+ sessionExportBtn.textContent = "\u5BFC\u51FA\u4F1A\u8BDD JSON";
639
+ sessionExportBtn.title = "\u5BFC\u51FA SessionBundle\uFF08\u53EF\u91CD\u653E\u4E8B\u4EF6\u65E5\u5FD7 + \u8BBE\u5907 + store \u5FEB\u7167\uFF09";
640
+ sessionExportBtn.addEventListener("click", () => hooks.onExportSession?.());
641
+ toolbar.appendChild(sessionExportBtn);
642
+ const sessionImportBtn = document.createElement("button");
643
+ sessionImportBtn.className = "pd-btn";
644
+ sessionImportBtn.textContent = "\u5BFC\u5165\u4F1A\u8BDD JSON";
645
+ sessionImportBtn.title = "\u5BFC\u5165 SessionBundle\uFF08\u6E05\u7A7A\u805A\u5408 \u2192 \u91CD\u653E\u4E8B\u4EF6\u5168\u89C6\u56FE\u91CD\u5EFA \u2192 \u72B6\u6001\u6062\u590D\uFF09";
646
+ const sessionFileInput = document.createElement("input");
647
+ sessionFileInput.type = "file";
648
+ sessionFileInput.accept = ".json,application/json";
649
+ sessionFileInput.style.display = "none";
650
+ sessionImportBtn.addEventListener("click", () => sessionFileInput.click());
651
+ sessionFileInput.addEventListener("change", () => {
652
+ const f = sessionFileInput.files?.[0];
653
+ sessionFileInput.value = "";
654
+ if (!f) return;
655
+ const reader = new FileReader();
656
+ reader.onload = () => hooks.onImportSession?.(String(reader.result ?? ""));
657
+ reader.readAsText(f);
658
+ });
659
+ toolbar.appendChild(sessionImportBtn);
660
+ toolbar.appendChild(sessionFileInput);
661
+ }
662
+ const stepInfo = document.createElement("span");
663
+ stepInfo.textContent = "\u6B65\u9AA4 " + data.steps.length + " \xB7 stores " + stores.length;
664
+ toolbar.appendChild(stepInfo);
665
+ container.appendChild(toolbar);
666
+ if (stores.length) {
667
+ const picker = document.createElement("div");
668
+ picker.className = "pd-store-picker";
669
+ for (const s of stores) {
670
+ const chip = document.createElement("button");
671
+ chip.className = "pd-store-chip" + (s.id === selected ? " pd-store-chip-active" : "");
672
+ chip.textContent = s.id;
673
+ chip.addEventListener("click", () => hooks.onSelectStore?.(s.id));
674
+ picker.appendChild(chip);
675
+ }
676
+ container.appendChild(picker);
677
+ }
678
+ const sel = stores.find((s) => s.id === selected);
679
+ if (sel) {
680
+ const card2 = document.createElement("div");
681
+ card2.className = "pd-store";
682
+ const head = document.createElement("div");
683
+ head.className = "pd-store-head";
684
+ head.textContent = selected + " \xB7 state";
685
+ card2.appendChild(head);
686
+ const inspector = document.createElement("div");
687
+ inspector.className = "pd-inspector";
688
+ renderKeyValue(inspector, "(root)", sel.state, 0, true, [], hooks.onEditValue ? { onEdit: (path, value) => hooks.onEditValue?.(selected, path, value) } : void 0);
689
+ card2.appendChild(inspector);
690
+ container.appendChild(card2);
691
+ const mine = data.steps.filter((st) => st.storeId === selected);
692
+ const tl = document.createElement("div");
693
+ tl.className = "pd-store-timeline";
694
+ const tlHead = document.createElement("div");
695
+ tlHead.className = "pd-section-head";
696
+ tlHead.textContent = "actions / patches\uFF08" + mine.length + "\uFF09";
697
+ tl.appendChild(tlHead);
698
+ if (mine.length === 0) {
699
+ const empty = document.createElement("div");
700
+ empty.className = "pd-empty";
701
+ empty.textContent = "\u6682\u65E0\u53D8\u66F4";
702
+ tl.appendChild(empty);
703
+ }
704
+ for (let i = mine.length - 1; i >= 0; i--) {
705
+ const st = mine[i];
706
+ const row = document.createElement("div");
707
+ row.className = "pd-tl-row";
708
+ const badge = document.createElement("span");
709
+ const isAction = st.type === "action";
710
+ badge.className = "pd-tl-badge " + (isAction ? "pd-tl-action" : "pd-tl-patch");
711
+ badge.textContent = isAction ? "action" : "patch";
712
+ const name = document.createElement("span");
713
+ name.className = "pd-tl-name";
714
+ name.textContent = String(st.payload?.name ?? "?");
715
+ const meta = document.createElement("span");
716
+ meta.className = "pd-tl-meta";
717
+ meta.textContent = "#" + st.index + " \xB7 " + st.timestamp + "ms";
718
+ row.appendChild(badge);
719
+ row.appendChild(name);
720
+ row.appendChild(meta);
721
+ const diff = diffLines(st.before, st.after);
722
+ if (diff.length > 0) {
723
+ attachTip(row, { title: st.type + " #" + st.index, lines: diff });
724
+ }
725
+ row.addEventListener("click", () => hooks.onTimeTravel?.(st.index));
726
+ tl.appendChild(row);
727
+ }
728
+ container.appendChild(tl);
729
+ }
730
+ if (data.steps.length > 0) {
731
+ const sliderRow = document.createElement("div");
732
+ sliderRow.className = "pd-toolbar";
733
+ const input = document.createElement("input");
734
+ input.type = "range";
735
+ input.min = "0";
736
+ input.max = String(data.steps.length - 1);
737
+ const initial = data.travelIndex !== void 0 && data.travelIndex >= 0 && data.travelIndex <= data.steps.length - 1 ? data.travelIndex : data.steps.length - 1;
738
+ input.value = String(initial);
739
+ input.className = "pd-range";
740
+ const hint = document.createElement("span");
741
+ hint.textContent = "\u56DE\u653E " + (initial + 1) + "/" + data.steps.length;
742
+ input.addEventListener("input", () => {
743
+ const i = Number(input.value);
744
+ hint.textContent = "\u56DE\u653E " + (i + 1) + "/" + data.steps.length;
745
+ });
746
+ input.addEventListener("change", () => {
747
+ hooks.onTimeTravel?.(Number(input.value));
748
+ });
749
+ sliderRow.appendChild(input);
750
+ sliderRow.appendChild(hint);
751
+ container.appendChild(sliderRow);
752
+ }
753
+ if (stores.length === 0) {
754
+ const empty = document.createElement("div");
755
+ empty.className = "pd-empty";
756
+ empty.textContent = "\u6682\u65E0 store\uFF08TraceBus store \u4E8B\u4EF6\u672A\u4E0A\u62A5\uFF09";
757
+ container.appendChild(empty);
758
+ }
759
+ }
760
+
761
+ // src/views/route.ts
762
+ function renderRoute(container, data) {
763
+ container.replaceChildren();
764
+ const records = data.records;
765
+ if (records.length === 0) {
766
+ const empty = document.createElement("div");
767
+ empty.className = "pd-empty";
768
+ empty.textContent = "\u6682\u65E0\u5BFC\u822A\u8BB0\u5F55";
769
+ container.appendChild(empty);
770
+ return;
771
+ }
772
+ for (let i = records.length - 1; i >= 0; i--) {
773
+ const r = records[i];
774
+ const row = document.createElement("div");
775
+ row.className = "pd-nav";
776
+ const path = document.createElement("div");
777
+ path.className = "pd-nav-path";
778
+ const from = document.createElement("span");
779
+ from.className = "pd-route";
780
+ from.textContent = r.from.path;
781
+ const arrow = document.createElement("span");
782
+ arrow.className = "pd-arrow";
783
+ arrow.textContent = " \u2192 ";
784
+ const to = document.createElement("span");
785
+ to.className = "pd-route";
786
+ to.textContent = r.to.path + (r.to.query ? "?" + JSON.stringify(r.to.query) : "");
787
+ path.appendChild(from);
788
+ path.appendChild(arrow);
789
+ path.appendChild(to);
790
+ const meta = document.createElement("span");
791
+ meta.className = "pd-nav-meta";
792
+ meta.textContent = r.durationMs + "ms" + (r.traceId ? " #" + r.traceId : "");
793
+ path.appendChild(meta);
794
+ row.appendChild(path);
795
+ const guards = document.createElement("div");
796
+ guards.className = "pd-guards";
797
+ for (const g of r.guards) {
798
+ const badge = document.createElement("span");
799
+ badge.className = "pd-guard pd-guard-" + g.result;
800
+ badge.textContent = g.name + " " + g.durationMs + "ms (" + g.result + ")";
801
+ attachTip(badge, {
802
+ title: "\u5B88\u536B " + g.name,
803
+ lines: [`\u7ED3\u679C: ${g.result}`, `\u8017\u65F6 ${g.durationMs}ms`]
804
+ });
805
+ guards.appendChild(badge);
806
+ }
807
+ row.appendChild(guards);
808
+ container.appendChild(row);
809
+ }
810
+ }
811
+
812
+ // src/views/errors.ts
813
+ function renderErrors(container, data) {
814
+ container.replaceChildren();
815
+ const reports = data.reports;
816
+ if (reports.length === 0) {
817
+ const empty = document.createElement("div");
818
+ empty.className = "pd-empty";
819
+ empty.textContent = "\u6682\u65E0\u5F02\u5E38\uFF08\u6839\u56E0\u9762\u677F\u7A7A\u95F2\uFF09";
820
+ container.appendChild(empty);
821
+ return;
822
+ }
823
+ for (const r of reports) {
824
+ const card2 = document.createElement("div");
825
+ card2.className = "pd-error-card";
826
+ const head = document.createElement("div");
827
+ head.className = "pd-error-head";
828
+ const title = document.createElement("div");
829
+ title.className = "pd-error-title";
830
+ title.textContent = r.rootCause.source + "." + r.rootCause.name + " @ " + r.rootCause.timestamp + "ms";
831
+ head.appendChild(title);
832
+ if (r.attribution) {
833
+ const attr = document.createElement("div");
834
+ attr.className = "pd-error-attr";
835
+ attr.textContent = "\u2691 " + r.attribution;
836
+ head.appendChild(attr);
837
+ }
838
+ card2.appendChild(head);
839
+ const chain = document.createElement("div");
840
+ chain.className = "pd-error-chain";
841
+ for (let i = r.chain.length - 1; i >= 0; i--) {
842
+ const c = r.chain[i];
843
+ const node = document.createElement("div");
844
+ const isRoot = c.source === r.rootCause.source && c.name === r.rootCause.name && c.timestamp === r.rootCause.timestamp;
845
+ node.className = "pd-chain-node" + (isRoot ? " pd-chain-root" : "");
846
+ node.textContent = c.source + "." + c.name;
847
+ chain.appendChild(node);
848
+ if (i > 0) {
849
+ const up = document.createElement("div");
850
+ up.className = "pd-chain-up";
851
+ up.textContent = "\u2191";
852
+ chain.appendChild(up);
853
+ }
854
+ }
855
+ card2.appendChild(chain);
856
+ const impact = document.createElement("div");
857
+ impact.className = "pd-error-impact";
858
+ for (const s of r.impactSources) {
859
+ const chip = document.createElement("span");
860
+ chip.className = "pd-chip";
861
+ chip.textContent = s;
862
+ impact.appendChild(chip);
863
+ }
864
+ card2.appendChild(impact);
865
+ const repro = document.createElement("ol");
866
+ repro.className = "pd-repro";
867
+ for (const step of r.repro) {
868
+ const li = document.createElement("li");
869
+ li.textContent = step;
870
+ repro.appendChild(li);
871
+ }
872
+ card2.appendChild(repro);
873
+ container.appendChild(card2);
874
+ }
875
+ }
876
+
877
+ // src/views/components.ts
878
+ function buildChildren(nodes) {
879
+ const byParent = /* @__PURE__ */ new Map();
880
+ const alive = new Set(nodes.map((n) => n.id));
881
+ for (const n of nodes) {
882
+ const key = n.parentId !== void 0 && alive.has(n.parentId) ? n.parentId : -1;
883
+ const list = byParent.get(key);
884
+ if (list) list.push(n);
885
+ else byParent.set(key, [n]);
886
+ }
887
+ return byParent;
888
+ }
889
+ function renderDetail(container, node, dom) {
890
+ const detail = document.createElement("div");
891
+ detail.className = "pd-cmp-detail";
892
+ const head = document.createElement("div");
893
+ head.className = "pd-section-head";
894
+ head.textContent = "#" + node.id + " " + node.name + " \xB7 \u8BE6\u60C5";
895
+ detail.appendChild(head);
896
+ const sections = [
897
+ ["props", node.props],
898
+ ["state", node.state]
899
+ ];
900
+ for (const [label, value] of sections) {
901
+ const box = document.createElement("div");
902
+ box.className = "pd-cmp-detail-section";
903
+ const labelEl = document.createElement("div");
904
+ labelEl.className = "pd-cmp-detail-label";
905
+ labelEl.textContent = label;
906
+ box.appendChild(labelEl);
907
+ if (value !== void 0 && value !== null && (typeof value !== "object" || Object.keys(value).length > 0)) {
908
+ renderKeyValue(box, "(root)", value, 0, true);
909
+ } else {
910
+ const empty = document.createElement("div");
911
+ empty.className = "pd-empty";
912
+ empty.textContent = "\u65E0 " + label;
913
+ box.appendChild(empty);
914
+ }
915
+ detail.appendChild(box);
916
+ }
917
+ if (dom) {
918
+ const box = document.createElement("div");
919
+ box.className = "pd-cmp-detail-section";
920
+ const labelEl = document.createElement("div");
921
+ labelEl.className = "pd-cmp-detail-label";
922
+ labelEl.textContent = "DOM";
923
+ box.appendChild(labelEl);
924
+ renderDomTree(box, dom, 0);
925
+ detail.appendChild(box);
926
+ }
927
+ container.appendChild(detail);
928
+ }
929
+ function renderDomTree(container, node, depth) {
930
+ const row = document.createElement("div");
931
+ row.className = "pd-dom-node";
932
+ row.style.paddingLeft = 10 + depth * 14 + "px";
933
+ const tag = document.createElement("span");
934
+ tag.className = "pd-dom-tag";
935
+ tag.textContent = node.tag;
936
+ row.appendChild(tag);
937
+ if (node.id) {
938
+ const idEl = document.createElement("span");
939
+ idEl.className = "pd-dom-id";
940
+ idEl.textContent = "#" + node.id;
941
+ row.appendChild(idEl);
942
+ }
943
+ if (node.cls?.length) {
944
+ const clsEl = document.createElement("span");
945
+ clsEl.className = "pd-dom-cls";
946
+ clsEl.textContent = "." + node.cls.join(".");
947
+ row.appendChild(clsEl);
948
+ }
949
+ container.appendChild(row);
950
+ if (node.children.length) {
951
+ const box = document.createElement("div");
952
+ box.className = "pd-dom-children";
953
+ for (const child of node.children) renderDomTree(box, child, depth + 1);
954
+ container.appendChild(box);
955
+ }
956
+ }
957
+ function renderComponents(container, data, hooks = {}) {
958
+ container.replaceChildren();
959
+ const nodes = data.nodes;
960
+ if (nodes.length === 0) {
961
+ const empty = document.createElement("div");
962
+ empty.className = "pd-empty";
963
+ empty.textContent = "\u6682\u65E0\u7EC4\u4EF6\uFF08installComponentTrace \u63A5\u5165\u540E\u51FA\u73B0\uFF09";
964
+ container.appendChild(empty);
965
+ return;
966
+ }
967
+ const byParent = buildChildren(nodes);
968
+ const selected = data.selectedId !== void 0 && nodes.some((n) => n.id === data.selectedId) ? data.selectedId : void 0;
969
+ function buildRow(n, depth, hasChildren) {
970
+ const row = document.createElement("div");
971
+ row.className = "pd-cmp-row" + (n.id === selected ? " pd-cmp-active" : "");
972
+ row.style.paddingLeft = depth * 16 + 6 + "px";
973
+ const toggle = document.createElement("span");
974
+ toggle.className = "pd-kv-toggle";
975
+ toggle.textContent = hasChildren ? "\u25BE" : "\xB7";
976
+ const name = document.createElement("span");
977
+ name.className = "pd-cmp-name";
978
+ name.textContent = n.name;
979
+ const meta = document.createElement("span");
980
+ meta.className = "pd-cmp-meta";
981
+ meta.textContent = "#" + n.id + (n.count > 1 ? " \xD7" + n.count : "");
982
+ row.appendChild(toggle);
983
+ row.appendChild(name);
984
+ row.appendChild(meta);
985
+ row.addEventListener("click", (e) => {
986
+ if (e.target.closest(".pd-cmp-row") !== row) return;
987
+ hooks.onSelect?.(n.id);
988
+ });
989
+ if (hasChildren) {
990
+ let collapsed = false;
991
+ let subtree = null;
992
+ const sub = document.createElement("div");
993
+ sub.className = "pd-cmp-children";
994
+ subtree = sub;
995
+ const kids = byParent.get(n.id) ?? [];
996
+ for (const k of kids) sub.appendChild(buildRow(k, depth + 1, (byParent.get(k.id)?.length ?? 0) > 0));
997
+ row.appendChild(sub);
998
+ toggle.addEventListener("click", (e) => {
999
+ e.stopPropagation();
1000
+ collapsed = !collapsed;
1001
+ toggle.textContent = collapsed ? "\u25B8" : "\u25BE";
1002
+ if (subtree) subtree.style.display = collapsed ? "none" : "block";
1003
+ });
1004
+ }
1005
+ return row;
1006
+ }
1007
+ const roots = byParent.get(-1) ?? [];
1008
+ for (const r of roots) {
1009
+ container.appendChild(buildRow(r, 0, (byParent.get(r.id)?.length ?? 0) > 0));
1010
+ }
1011
+ if (selected !== void 0) {
1012
+ const sel = nodes.find((n) => n.id === selected);
1013
+ if (sel) renderDetail(container, sel, data.dom);
1014
+ }
1015
+ }
1016
+
1017
+ // src/views/pages.ts
1018
+ function renderPages(container, data) {
1019
+ container.replaceChildren();
1020
+ const routes = data.routes;
1021
+ if (routes.length === 0) {
1022
+ const empty = document.createElement("div");
1023
+ empty.className = "pd-empty";
1024
+ empty.textContent = "\u6682\u65E0\u8DEF\u7531\u8868\uFF08Proteus.appInfo \u6CE8\u5165\u540E\u51FA\u73B0\uFF09";
1025
+ container.appendChild(empty);
1026
+ return;
1027
+ }
1028
+ const stack = data.stack ?? [];
1029
+ if (stack.length) {
1030
+ const stackBox = document.createElement("div");
1031
+ stackBox.className = "pd-page-stack";
1032
+ const head = document.createElement("div");
1033
+ head.className = "pd-section-head";
1034
+ head.textContent = "\u9875\u9762\u6808\uFF08" + stack.length + "\uFF09";
1035
+ stackBox.appendChild(head);
1036
+ for (let i = stack.length - 1; i >= 0; i--) {
1037
+ const route = stack[i]?.route ?? "?";
1038
+ const row = document.createElement("div");
1039
+ row.className = "pd-page-row" + (i === stack.length - 1 ? " pd-page-current" : "");
1040
+ const depth = document.createElement("span");
1041
+ depth.className = "pd-page-depth";
1042
+ depth.textContent = String(stack.length - 1 - i);
1043
+ const name = document.createElement("span");
1044
+ name.className = "pd-page-name";
1045
+ name.textContent = route;
1046
+ row.appendChild(depth);
1047
+ row.appendChild(name);
1048
+ stackBox.appendChild(row);
1049
+ }
1050
+ container.appendChild(stackBox);
1051
+ }
1052
+ const main = routes.filter((r) => !r.subPackage);
1053
+ const subs = [];
1054
+ const subNames = /* @__PURE__ */ new Set();
1055
+ for (const r of routes) if (r.subPackage && !subNames.has(r.subPackage)) subNames.add(r.subPackage);
1056
+ for (const s of subNames) subs.push({ name: s, routes: routes.filter((r) => r.subPackage === s) });
1057
+ function renderGroup(title, list) {
1058
+ const group = document.createElement("div");
1059
+ group.className = "pd-page-group";
1060
+ const head = document.createElement("div");
1061
+ head.className = "pd-section-head";
1062
+ head.textContent = title + "\uFF08" + list.length + "\uFF09";
1063
+ group.appendChild(head);
1064
+ for (const r of list) {
1065
+ const row = document.createElement("div");
1066
+ row.className = "pd-page-row";
1067
+ const name = document.createElement("span");
1068
+ name.className = "pd-page-name";
1069
+ name.textContent = r.name;
1070
+ const meta = document.createElement("span");
1071
+ meta.className = "pd-page-meta";
1072
+ meta.textContent = r.path + (r.meta?.isTab ? " \xB7 tab" : "") + (r.meta?.title ? " \xB7 " + r.meta.title : "");
1073
+ row.appendChild(name);
1074
+ row.appendChild(meta);
1075
+ group.appendChild(row);
1076
+ }
1077
+ return group;
1078
+ }
1079
+ container.appendChild(renderGroup("\u4E3B\u5305\u9875\u9762", main));
1080
+ for (const s of subs) container.appendChild(renderGroup("\u5206\u5305 " + s.name, s.routes));
1081
+ }
1082
+
1083
+ // src/views/graph.ts
1084
+ function buildTree(routes) {
1085
+ const byName = /* @__PURE__ */ new Map();
1086
+ for (const r of routes) byName.set(r.name, r);
1087
+ const roots = [];
1088
+ const nodeMap = /* @__PURE__ */ new Map();
1089
+ for (const r of routes) nodeMap.set(r.name, { route: r, children: [] });
1090
+ for (const r of routes) {
1091
+ const node = nodeMap.get(r.name);
1092
+ const parent = r.parent !== void 0 && nodeMap.has(r.parent) ? nodeMap.get(r.parent) : void 0;
1093
+ if (parent) parent.children.push(node);
1094
+ else roots.push(node);
1095
+ }
1096
+ return roots;
1097
+ }
1098
+ function buildBranch(node, prefix, isLast, out, depth) {
1099
+ const row = document.createElement("div");
1100
+ row.className = "pd-graph-node" + (depth === 0 ? " pd-graph-root" : "");
1101
+ const line = document.createElement("span");
1102
+ line.className = "pd-graph-line";
1103
+ line.textContent = prefix + (isLast ? "\u2514\u2500 " : "\u251C\u2500 ");
1104
+ const name = document.createElement("span");
1105
+ name.className = "pd-graph-name";
1106
+ name.textContent = node.route.name;
1107
+ const meta = document.createElement("span");
1108
+ meta.className = "pd-graph-meta";
1109
+ const parts = [node.route.path];
1110
+ if (node.route.meta?.isTab) parts.push("tab");
1111
+ if (node.route.subPackage) parts.push("\u5206\u5305 " + node.route.subPackage);
1112
+ meta.textContent = parts.join(" \xB7 ");
1113
+ row.appendChild(line);
1114
+ row.appendChild(name);
1115
+ row.appendChild(meta);
1116
+ out.appendChild(row);
1117
+ const kids = node.children;
1118
+ for (let i = 0; i < kids.length; i++) {
1119
+ buildBranch(kids[i], prefix + (isLast ? " " : "\u2502 "), i === kids.length - 1, out, depth + 1);
1120
+ }
1121
+ }
1122
+ function renderGraph(container, data) {
1123
+ container.replaceChildren();
1124
+ const routes = data.routes;
1125
+ if (routes.length === 0) {
1126
+ const empty = document.createElement("div");
1127
+ empty.className = "pd-empty";
1128
+ empty.textContent = "\u6682\u65E0\u8DEF\u7531\u8868\uFF08Proteus.appInfo \u6CE8\u5165\u540E\u51FA\u73B0\uFF09";
1129
+ container.appendChild(empty);
1130
+ return;
1131
+ }
1132
+ const roots = buildTree(routes);
1133
+ const box = document.createElement("div");
1134
+ box.className = "pd-graph";
1135
+ const head = document.createElement("div");
1136
+ head.className = "pd-section-head";
1137
+ const subCount = new Set(routes.map((r) => r.subPackage).filter((s) => s !== void 0)).size;
1138
+ head.textContent = "\u8DEF\u7531\u4F9D\u8D56\u6811\uFF08" + routes.length + " \u9875" + (subCount ? " \xB7 " + subCount + " \u5206\u5305" : "") + "\uFF09";
1139
+ box.appendChild(head);
1140
+ for (let i = 0; i < roots.length; i++) {
1141
+ buildBranch(roots[i], "", i === roots.length - 1, box, 0);
1142
+ }
1143
+ container.appendChild(box);
1144
+ }
1145
+
1146
+ // src/views/device.ts
1147
+ function fmtBytes(b) {
1148
+ if (!b || b <= 0) return "0 B";
1149
+ const units = ["B", "KB", "MB", "GB"];
1150
+ let i = 0;
1151
+ let v = b;
1152
+ while (v >= 1024 && i < units.length - 1) {
1153
+ v /= 1024;
1154
+ i += 1;
1155
+ }
1156
+ return v.toFixed(i === 0 ? 0 : 1) + " " + units[i];
1157
+ }
1158
+ function renderDevice(container, data) {
1159
+ container.replaceChildren();
1160
+ const info = data.info;
1161
+ if (!info) {
1162
+ const empty = document.createElement("div");
1163
+ empty.className = "pd-empty";
1164
+ empty.textContent = "\u6682\u65E0\u8BBE\u5907\u4FE1\u606F\uFF08Proteus.deviceInfo \u6CE8\u5165\u540E\u51FA\u73B0\uFF09";
1165
+ container.appendChild(empty);
1166
+ return;
1167
+ }
1168
+ const overview = document.createElement("div");
1169
+ overview.className = "pd-dev-overview";
1170
+ const cards = [
1171
+ { label: "\u5E73\u53F0", value: info.platform || "\u2014" },
1172
+ { label: "\u57FA\u7840\u5E93", value: info.libVersion || "\u2014" },
1173
+ { label: "\u5C4F\u5E55", value: info.screen ? info.screen.width + "\xD7" + info.screen.height + " @" + info.screen.dpr + "x" : "\u2014" }
1174
+ ];
1175
+ if (info.memory) {
1176
+ const total = info.memory.totalJSHeapSize;
1177
+ const usedPct = total > 0 ? info.memory.usedJSHeapSize / total * 100 : 0;
1178
+ cards.push({ label: "JS \u5806", value: fmtBytes(info.memory.usedJSHeapSize) + " / " + fmtBytes(info.memory.jsHeapLimit), hint: usedPct.toFixed(1) + "% \u5DF2\u7528\uFF08total\uFF09" });
1179
+ }
1180
+ for (const c of cards) {
1181
+ const card2 = document.createElement("div");
1182
+ card2.className = "pd-dev-card";
1183
+ const label = document.createElement("div");
1184
+ label.className = "pd-dev-card-label";
1185
+ label.textContent = c.label;
1186
+ const value = document.createElement("div");
1187
+ value.className = "pd-dev-card-value";
1188
+ value.textContent = c.value;
1189
+ card2.appendChild(label);
1190
+ card2.appendChild(value);
1191
+ if (c.hint) {
1192
+ const hint = document.createElement("div");
1193
+ hint.className = "pd-dev-card-hint";
1194
+ hint.textContent = c.hint;
1195
+ card2.appendChild(hint);
1196
+ }
1197
+ overview.appendChild(card2);
1198
+ }
1199
+ container.appendChild(overview);
1200
+ if (info.userAgent) {
1201
+ const ua = document.createElement("div");
1202
+ ua.className = "pd-dev-ua";
1203
+ ua.textContent = info.userAgent;
1204
+ ua.title = info.userAgent;
1205
+ container.appendChild(ua);
1206
+ }
1207
+ const samples = data.memory;
1208
+ if (samples.length) {
1209
+ const box = document.createElement("div");
1210
+ box.className = "pd-dev-memory";
1211
+ const head = document.createElement("div");
1212
+ head.className = "pd-section-head";
1213
+ head.textContent = "\u5185\u5B58\u66F2\u7EBF\uFF08" + samples.length + " \u91C7\u6837 \xB7 \u9762\u677F\u8FDB\u7A0B\uFF09";
1214
+ box.appendChild(head);
1215
+ const max = Math.max.apply(null, samples.map((s) => s.total)) || 1;
1216
+ const chart = document.createElement("div");
1217
+ chart.className = "pd-dev-mem-chart";
1218
+ for (const s of samples) {
1219
+ const col = document.createElement("div");
1220
+ col.className = "pd-dev-mem-col";
1221
+ const usedH = Math.round(s.used / max * 100);
1222
+ const used = document.createElement("div");
1223
+ used.className = "pd-dev-mem-used";
1224
+ used.style.height = Math.max(1, usedH) + "%";
1225
+ used.title = "used " + fmtBytes(s.used);
1226
+ const total = document.createElement("div");
1227
+ total.className = "pd-dev-mem-total";
1228
+ total.style.height = Math.round(s.total / max * 100) + "%";
1229
+ total.title = "total " + fmtBytes(s.total);
1230
+ col.appendChild(total);
1231
+ col.appendChild(used);
1232
+ chart.appendChild(col);
1233
+ }
1234
+ box.appendChild(chart);
1235
+ const latest = samples[samples.length - 1];
1236
+ const stat = document.createElement("div");
1237
+ stat.className = "pd-dev-mem-stat";
1238
+ stat.textContent = "used " + fmtBytes(latest.used) + " / total " + fmtBytes(latest.total) + " / limit " + fmtBytes(latest.limit);
1239
+ box.appendChild(stat);
1240
+ container.appendChild(box);
1241
+ }
1242
+ const caps = Array.isArray(info.capabilities) ? info.capabilities : [];
1243
+ if (caps.length) {
1244
+ const table = document.createElement("div");
1245
+ table.className = "pd-dev-caps";
1246
+ const head = document.createElement("div");
1247
+ head.className = "pd-section-head";
1248
+ head.textContent = "\u80FD\u529B\uFF08" + caps.length + " \xB7 " + info.platform + "\uFF09";
1249
+ table.appendChild(head);
1250
+ for (const c of caps) {
1251
+ const row = document.createElement("div");
1252
+ row.className = "pd-dev-cap" + (c.supported ? " pd-dev-cap-ok" : " pd-dev-cap-no");
1253
+ const mark = document.createElement("span");
1254
+ mark.className = "pd-dev-cap-mark";
1255
+ mark.textContent = c.supported ? "\u2705" : "\u274C";
1256
+ const name = document.createElement("span");
1257
+ name.className = "pd-dev-cap-name";
1258
+ name.textContent = c.capability;
1259
+ const meta = document.createElement("span");
1260
+ meta.className = "pd-dev-cap-meta";
1261
+ const parts = [];
1262
+ parts.push(c.platforms.join("/"));
1263
+ if (c.required) parts.push("required");
1264
+ if (c.runsInWorklet) parts.push("worklet");
1265
+ if (c.fallback) parts.push("\u2192 " + c.fallback);
1266
+ if (c.priority) parts.push("p" + c.priority);
1267
+ meta.textContent = parts.join(" \xB7 ");
1268
+ row.appendChild(mark);
1269
+ row.appendChild(name);
1270
+ row.appendChild(meta);
1271
+ table.appendChild(row);
1272
+ }
1273
+ container.appendChild(table);
1274
+ }
1275
+ }
1276
+
1277
+ // src/views/ownership.ts
1278
+ function fmtBytes2(n) {
1279
+ if (n >= 1024 * 1024) return (n / (1024 * 1024)).toFixed(1) + " MB";
1280
+ if (n >= 1024) return (n / 1024).toFixed(1) + " KB";
1281
+ return n + " B";
1282
+ }
1283
+ function card(parent, cls, title) {
1284
+ const el = document.createElement("div");
1285
+ el.className = cls;
1286
+ const t = document.createElement("div");
1287
+ t.className = "pd-own-card-title";
1288
+ t.textContent = title;
1289
+ el.appendChild(t);
1290
+ parent.appendChild(el);
1291
+ return el;
1292
+ }
1293
+ function renderOwnership(container, data) {
1294
+ container.replaceChildren();
1295
+ container.classList.add("pd-ownership");
1296
+ if (!data || !data.summary) {
1297
+ const empty = document.createElement("div");
1298
+ empty.className = "pd-empty";
1299
+ empty.textContent = "\u6682\u65E0\u6240\u6709\u6743\u6570\u636E\uFF08installProteusDevtools({ ownership: true }) \u542F\u7528\uFF0C\u6216\u4E1A\u52A1\u5728\u521B\u5EFA OwnershipGraph \u65F6\u6302\u63A5\uFF09";
1300
+ container.appendChild(empty);
1301
+ return;
1302
+ }
1303
+ const summary = card(container, "pd-own-summary", "\u6240\u6709\u6743\u6982\u8981");
1304
+ const stats = document.createElement("div");
1305
+ stats.className = "pd-own-stats";
1306
+ stats.textContent = `${data.summary.alive} alive / ${data.summary.total} total \xB7 ${fmtBytes2(data.summary.bytesAlive)}`;
1307
+ summary.appendChild(stats);
1308
+ const types = Object.entries(data.summary.byType);
1309
+ if (types.length > 0) {
1310
+ const typeLine = document.createElement("div");
1311
+ typeLine.className = "pd-own-types";
1312
+ typeLine.textContent = types.map(([type, c]) => `${type}: ${c.alive}/${c.allocated}`).join(" \xB7 ");
1313
+ summary.appendChild(typeLine);
1314
+ }
1315
+ const d = data.diagnosis;
1316
+ const alerts = card(container, "pd-own-alerts", "\u68C0\u6D4B");
1317
+ const alertCount = d.orphans.length + d.leaks.length + d.longBorrows.length + d.crossPageRefs.length;
1318
+ if (alertCount === 0) {
1319
+ const ok = document.createElement("div");
1320
+ ok.className = "pd-own-ok";
1321
+ ok.textContent = "\u2705 \u65E0\u5F02\u5E38\uFF08\u65E0\u65E0\u4E3B\u8D44\u6E90 / \u6CC4\u6F0F\u8DEF\u5F84 / \u957F\u671F\u501F\u7528 / \u8DE8\u9875\u5F3A\u5F15\u7528\uFF09";
1322
+ alerts.appendChild(ok);
1323
+ } else {
1324
+ for (const o of d.orphans) {
1325
+ const row = document.createElement("div");
1326
+ row.className = "pd-own-alert pd-own-alert-orphan";
1327
+ row.textContent = `\u{1F534} \u65E0\u4E3B\u8D44\u6E90 ${o.id}\uFF08${o.type}\uFF0C${fmtBytes2(o.byteSize)}\uFF09${o.sourceLocation ? "\u{1F4CD} " + o.sourceLocation : ""}\u2014\u2014\u5FC5\u7136\u6CC4\u6F0F\uFF08\u6846\u67B6 bug \u6216\u672A\u767B\u8BB0\uFF09`;
1328
+ alerts.appendChild(row);
1329
+ }
1330
+ for (const l of d.leaks) {
1331
+ const row = document.createElement("div");
1332
+ row.className = "pd-own-alert pd-own-alert-leak";
1333
+ row.textContent = `\u26A0\uFE0F \u6CC4\u6F0F\u8DEF\u5F84 ${l.resourceId}\uFF08${l.type}\uFF0C${fmtBytes2(l.byteSize)}\uFF09${l.sourceLocation ? "\u{1F4CD} " + l.sourceLocation : ""}`;
1334
+ alerts.appendChild(row);
1335
+ for (const hop of l.referenceChain) {
1336
+ const chain = document.createElement("div");
1337
+ chain.className = "pd-own-chain";
1338
+ chain.textContent = " " + hop;
1339
+ alerts.appendChild(chain);
1340
+ }
1341
+ }
1342
+ for (const b of d.longBorrows) {
1343
+ const row = document.createElement("div");
1344
+ row.className = "pd-own-alert pd-own-alert-longborrow";
1345
+ row.textContent = `\u{1F7E1} \u957F\u671F\u501F\u7528 ${b.resourceId} \u2190 borrowed by ${b.borrowedBy}\uFF08owner ${b.owner ?? "\u65E0\u4E3B"}\uFF09`;
1346
+ alerts.appendChild(row);
1347
+ }
1348
+ for (const c of d.crossPageRefs) {
1349
+ const row = document.createElement("div");
1350
+ row.className = "pd-own-alert pd-own-alert-crosspage";
1351
+ row.textContent = `\u26A0\uFE0F \u8DE8\u9875\u5F3A\u5F15\u7528 ${c.resourceId}\uFF08owner ${c.owner ?? "\u65E0\u4E3B"}\uFF09\u2190 \u5F3A\u6301\u6709 by ${c.heldBy}\u2014\u2014\u5E94 transferTo/weak`;
1352
+ alerts.appendChild(row);
1353
+ }
1354
+ }
1355
+ const resCard = card(container, "pd-own-resources", "\u8D44\u6E90\uFF08\u6309 owner\uFF09");
1356
+ if (data.resources.length === 0) {
1357
+ const empty = document.createElement("div");
1358
+ empty.className = "pd-empty";
1359
+ empty.textContent = "\u65E0\u5B58\u6D3B\u8D44\u6E90";
1360
+ resCard.appendChild(empty);
1361
+ }
1362
+ for (const group of data.resources) {
1363
+ const ownerRow = document.createElement("div");
1364
+ ownerRow.className = "pd-own-owner";
1365
+ const bytes = group.items.reduce((s, i) => s + i.byteSize, 0);
1366
+ ownerRow.textContent = `\u25BC ${group.owner}\uFF08${group.items.length} \u8D44\u6E90\uFF0C${fmtBytes2(bytes)}\uFF09`;
1367
+ resCard.appendChild(ownerRow);
1368
+ for (const item of group.items) {
1369
+ const row = document.createElement("div");
1370
+ row.className = "pd-own-resource";
1371
+ const stateMark = group.owner === "\uFF08\u65E0\u4E3B\uFF09" ? "\u{1F534}" : "\u{1F7E2}";
1372
+ const borrowInfo = item.borrowedBy.length > 0 ? ` \u2190 \u{1F7E1} ${item.borrowedBy.join(", ")}` : "";
1373
+ row.textContent = ` ${stateMark} ${item.type} ${fmtBytes2(item.byteSize)}${item.sourceLocation ? " \u{1F4CD} " + item.sourceLocation : ""}${borrowInfo}`;
1374
+ resCard.appendChild(row);
1375
+ }
1376
+ }
1377
+ const tlCard = card(container, "pd-own-timeline", `\u65F6\u95F4\u7EBF${data.timeline.truncated ? `\uFF08\u8FD1 ${data.timeline.events.length} \u6761\uFF09` : ""}`);
1378
+ if (data.timeline.events.length === 0) {
1379
+ const empty = document.createElement("div");
1380
+ empty.className = "pd-empty";
1381
+ empty.textContent = "\u6682\u65E0 alloc/drop \u8BB0\u5F55";
1382
+ tlCard.appendChild(empty);
1383
+ }
1384
+ for (const e of data.timeline.events) {
1385
+ const row = document.createElement("div");
1386
+ if (e.kind === "alloc") {
1387
+ const unpaired = data.timeline.unpairedIds.includes(e.id ?? "");
1388
+ row.className = "pd-own-tl" + (unpaired ? " pd-own-tl-unpaired" : "");
1389
+ row.textContent = `\u2191 alloc ${e.type ?? ""} ${fmtBytes2(e.byteSize ?? 0)}${e.sourceLocation ? " \u{1F4CD} " + e.sourceLocation : ""}${unpaired ? " \u26A0\uFE0F \u672A\u914D\u5BF9\uFF08\u53EF\u7591\uFF09" : ""}`;
1390
+ } else if (e.kind === "drop") {
1391
+ row.className = "pd-own-tl";
1392
+ row.textContent = `\u2193 drop ${e.type ?? ""} ${fmtBytes2(e.byteSize ?? 0)}${e.matchedAllocId ? `\uFF08\u2194 ${e.matchedAllocId}\uFF09` : ""}`;
1393
+ } else if (e.kind === "moved") {
1394
+ row.className = "pd-own-tl";
1395
+ row.textContent = `\u2192 moved ${e.type ?? ""}\uFF08owner ${e.owner ?? "\u2014"}\uFF09`;
1396
+ } else {
1397
+ row.className = "pd-own-tl";
1398
+ row.textContent = `${e.kind} ${e.from ?? ""} \u2192 ${e.to ?? ""}`;
1399
+ }
1400
+ tlCard.appendChild(row);
1401
+ }
1402
+ }
1403
+
1404
+ // src/snapshot-io.ts
1405
+ var SENSITIVE_PATTERNS = ["password", "token", "authorization", "idcard", "phone"];
1406
+ function isSensitiveKey(key) {
1407
+ const k = key.toLowerCase();
1408
+ for (let i = 0; i < SENSITIVE_PATTERNS.length; i++) {
1409
+ if (k.indexOf(SENSITIVE_PATTERNS[i]) >= 0) return true;
1410
+ }
1411
+ return false;
1412
+ }
1413
+ function findSensitiveKeys(stores) {
1414
+ const out = [];
1415
+ const walk = (value) => {
1416
+ if (value === null || typeof value !== "object") return [];
1417
+ const hits = [];
1418
+ const obj = value;
1419
+ for (const k of Object.keys(obj)) {
1420
+ if (isSensitiveKey(k)) hits.push(k);
1421
+ hits.push.apply(hits, walk(obj[k]));
1422
+ }
1423
+ return hits;
1424
+ };
1425
+ for (const s of stores) {
1426
+ const keys = walk(s.state);
1427
+ if (keys.length) out.push({ storeId: s.id, keys: Array.from(new Set(keys)) });
1428
+ }
1429
+ return out;
1430
+ }
1431
+ function serializeStoreSnapshot(input) {
1432
+ const data = {
1433
+ kind: "proteus-store-snapshot",
1434
+ version: 1,
1435
+ exportedAt: Date.now(),
1436
+ stores: input.stores,
1437
+ steps: input.steps
1438
+ };
1439
+ return JSON.stringify(data, null, 2);
1440
+ }
1441
+ function parseStoreSnapshot(json) {
1442
+ let data;
1443
+ try {
1444
+ data = JSON.parse(json);
1445
+ } catch {
1446
+ return null;
1447
+ }
1448
+ if (data === null || typeof data !== "object") return null;
1449
+ const d = data;
1450
+ if (d.kind !== "proteus-store-snapshot" || !Array.isArray(d.stores)) return null;
1451
+ const stores = [];
1452
+ for (const s of d.stores) {
1453
+ if (s && typeof s.id === "string" && s.state !== null && typeof s.state === "object" && !Array.isArray(s.state)) {
1454
+ stores.push({ id: s.id, state: s.state });
1455
+ }
1456
+ }
1457
+ const steps = [];
1458
+ if (Array.isArray(d.steps)) {
1459
+ for (const st of d.steps) {
1460
+ if (st && typeof st.index === "number" && typeof st.storeId === "string" && (st.type === "patch" || st.type === "action")) {
1461
+ steps.push({
1462
+ index: st.index,
1463
+ storeId: st.storeId,
1464
+ type: st.type,
1465
+ name: typeof st.name === "string" ? st.name : st.type,
1466
+ payload: st.payload,
1467
+ timestamp: typeof st.timestamp === "number" ? st.timestamp : 0
1468
+ });
1469
+ }
1470
+ }
1471
+ }
1472
+ return { stores, steps };
1473
+ }
1474
+
1475
+ // src/session-io.ts
1476
+ var VALID_SOURCES = ["lifecycle", "router", "store", "api", "capability", "compiler", "component", "hmr"];
1477
+ var VALID_PHASES = ["start", "end", "point", "error"];
1478
+ function serializeSession(input) {
1479
+ const bundle = {
1480
+ kind: "proteus-session",
1481
+ version: 1,
1482
+ exportedAt: Date.now(),
1483
+ meta: { eventCount: input.events.length },
1484
+ events: input.events,
1485
+ device: input.device,
1486
+ stores: input.stores,
1487
+ steps: input.steps
1488
+ };
1489
+ return JSON.stringify(bundle, null, 2);
1490
+ }
1491
+ function parseSession(json) {
1492
+ let data;
1493
+ try {
1494
+ data = JSON.parse(json);
1495
+ } catch {
1496
+ return null;
1497
+ }
1498
+ if (data === null || typeof data !== "object") return null;
1499
+ const d = data;
1500
+ if (d.kind !== "proteus-session" || d.version !== 1 || !Array.isArray(d.events)) return null;
1501
+ const events = [];
1502
+ for (const e of d.events) {
1503
+ if (!e || typeof e !== "object") continue;
1504
+ const ev = e;
1505
+ if (typeof ev.source !== "string" || VALID_SOURCES.indexOf(ev.source) < 0) continue;
1506
+ if (typeof ev.phase !== "string" || VALID_PHASES.indexOf(ev.phase) < 0) continue;
1507
+ if (typeof ev.name !== "string" || !ev.name) continue;
1508
+ events.push({
1509
+ source: ev.source,
1510
+ phase: ev.phase,
1511
+ name: ev.name,
1512
+ payload: ev.payload,
1513
+ timestamp: typeof ev.timestamp === "number" ? ev.timestamp : Date.now(),
1514
+ traceId: typeof ev.traceId === "string" ? ev.traceId : void 0
1515
+ });
1516
+ }
1517
+ const stores = [];
1518
+ if (Array.isArray(d.stores)) {
1519
+ for (const s of d.stores) {
1520
+ if (s && typeof s.id === "string" && s.state !== null && typeof s.state === "object" && !Array.isArray(s.state)) {
1521
+ stores.push({ id: s.id, state: s.state });
1522
+ }
1523
+ }
1524
+ }
1525
+ const steps = [];
1526
+ if (Array.isArray(d.steps)) {
1527
+ for (const st of d.steps) {
1528
+ if (st && typeof st.index === "number" && typeof st.storeId === "string" && (st.type === "patch" || st.type === "action")) {
1529
+ steps.push({
1530
+ index: st.index,
1531
+ storeId: st.storeId,
1532
+ type: st.type,
1533
+ name: typeof st.name === "string" ? st.name : st.type,
1534
+ payload: st.payload,
1535
+ timestamp: typeof st.timestamp === "number" ? st.timestamp : 0
1536
+ });
1537
+ }
1538
+ }
1539
+ }
1540
+ return { events, device: d.device, stores, steps };
1541
+ }
1542
+
1543
+ // src/plugins.ts
1544
+ function createMemoryStorage() {
1545
+ const map = /* @__PURE__ */ new Map();
1546
+ return {
1547
+ get: (key) => map.get(key),
1548
+ set: (key, value) => {
1549
+ map.set(key, value);
1550
+ }
1551
+ };
1552
+ }
1553
+ function createCommandRegistry() {
1554
+ const commands = /* @__PURE__ */ new Map();
1555
+ return {
1556
+ register(id, run) {
1557
+ commands.set(id, run);
1558
+ },
1559
+ run(id) {
1560
+ commands.get(id)?.();
1561
+ },
1562
+ list() {
1563
+ return Array.from(commands.keys());
1564
+ }
1565
+ };
1566
+ }
1567
+ function resolveActivationOrder(plugins) {
1568
+ const byName = new Map(plugins.map((p) => [p.name, p]));
1569
+ const deps = /* @__PURE__ */ new Map();
1570
+ for (const p of plugins) deps.set(p.name, (p.peerDependencies ?? []).filter((d) => byName.has(d)));
1571
+ const indegree = /* @__PURE__ */ new Map();
1572
+ for (const [name, ds] of deps) indegree.set(name, ds.length);
1573
+ const queue = [];
1574
+ for (const [name, d] of indegree) if (d === 0) queue.push(name);
1575
+ const order = [];
1576
+ while (queue.length) {
1577
+ const name = queue.shift();
1578
+ order.push(name);
1579
+ for (const other of deps.keys()) {
1580
+ const list = deps.get(other);
1581
+ if (list.includes(name)) {
1582
+ const nd = indegree.get(other) - 1;
1583
+ indegree.set(other, nd);
1584
+ if (nd === 0) queue.push(other);
1585
+ }
1586
+ }
1587
+ }
1588
+ const remaining = plugins.map((p) => p.name).filter((n) => !order.includes(n));
1589
+ if (remaining.length) {
1590
+ const start = remaining[0];
1591
+ const cyclePath = [];
1592
+ const seen = /* @__PURE__ */ new Set();
1593
+ let cur = start;
1594
+ while (cur && !seen.has(cur)) {
1595
+ seen.add(cur);
1596
+ cyclePath.push(cur);
1597
+ cur = (deps.get(cur) ?? []).find((d) => remaining.includes(d)) ?? null;
1598
+ }
1599
+ if (cur) cyclePath.push(cur);
1600
+ return { order, cycle: cyclePath };
1601
+ }
1602
+ return { order, cycle: null };
1603
+ }
1604
+ function createPluginRegistry(initial = []) {
1605
+ const plugins = /* @__PURE__ */ new Map();
1606
+ const statuses = /* @__PURE__ */ new Map();
1607
+ const errors = /* @__PURE__ */ new Map();
1608
+ for (const p of initial) {
1609
+ plugins.set(p.name, p);
1610
+ statuses.set(p.name, "registered");
1611
+ }
1612
+ return {
1613
+ register(plugin) {
1614
+ plugins.set(plugin.name, plugin);
1615
+ statuses.set(plugin.name, "registered");
1616
+ },
1617
+ unregister(name) {
1618
+ plugins.delete(name);
1619
+ statuses.delete(name);
1620
+ errors.delete(name);
1621
+ },
1622
+ async activateAll(ctx) {
1623
+ const all = Array.from(plugins.values());
1624
+ const { order, cycle } = resolveActivationOrder(all);
1625
+ if (cycle) throw new Error("\u63D2\u4EF6\u5FAA\u73AF\u4F9D\u8D56\uFF1A" + cycle.join(" \u2192 ") + "\uFF08\u8BF7\u89E3\u9664\u4F9D\u8D56\u540E\u518D\u6FC0\u6D3B\uFF09");
1626
+ for (const name of order) {
1627
+ const plugin = plugins.get(name);
1628
+ try {
1629
+ await plugin.setup(ctx(plugin));
1630
+ statuses.set(name, "active");
1631
+ errors.delete(name);
1632
+ } catch (err) {
1633
+ statuses.set(name, "crashed");
1634
+ errors.set(name, err instanceof Error ? err.message : String(err));
1635
+ }
1636
+ }
1637
+ return Array.from(plugins.keys()).map((name) => ({
1638
+ name,
1639
+ version: plugins.get(name).version,
1640
+ status: statuses.get(name) ?? "registered",
1641
+ error: errors.get(name)
1642
+ }));
1643
+ },
1644
+ list() {
1645
+ return Array.from(plugins.keys()).map((name) => ({
1646
+ name,
1647
+ version: plugins.get(name).version,
1648
+ status: statuses.get(name) ?? "registered",
1649
+ error: errors.get(name)
1650
+ }));
1651
+ },
1652
+ get: (name) => plugins.get(name)
1653
+ };
1654
+ }
1655
+
1656
+ // src/panel.ts
1657
+ var VIEWS = ["timeline", "flamegraph", "state", "route", "errors", "components", "pages", "graph", "device", "ownership"];
1658
+ var VIEW_ICONS = {
1659
+ timeline: "\u229E",
1660
+ flamegraph: "\u25A4",
1661
+ state: "\u2630",
1662
+ route: "\u21C4",
1663
+ errors: "\u2715",
1664
+ components: "\u25EB",
1665
+ pages: "\u25A6",
1666
+ graph: "\u232C",
1667
+ device: "\u2699",
1668
+ ownership: "\u2B21"
1669
+ };
1670
+ function createDevtoolsPanel(root, options) {
1671
+ const { source, onTimeTravel } = options;
1672
+ root.classList.add("pd-panel");
1673
+ root.replaceChildren();
1674
+ const header = document.createElement("div");
1675
+ header.className = "pd-header";
1676
+ const title = document.createElement("div");
1677
+ title.className = "pd-header-title";
1678
+ title.textContent = "Proteus DevTools";
1679
+ header.appendChild(title);
1680
+ const status = document.createElement("div");
1681
+ status.className = "pd-header-status";
1682
+ const dot = document.createElement("span");
1683
+ dot.className = "pd-dot";
1684
+ const statusText = document.createElement("span");
1685
+ statusText.textContent = "\u8FDE\u63A5\u4E2D";
1686
+ status.appendChild(dot);
1687
+ status.appendChild(statusText);
1688
+ header.appendChild(status);
1689
+ root.appendChild(header);
1690
+ const bodyRow = document.createElement("div");
1691
+ bodyRow.className = "pd-body-row";
1692
+ const sidebar = document.createElement("div");
1693
+ sidebar.className = "pd-sidebar";
1694
+ const content = document.createElement("div");
1695
+ content.className = "pd-content";
1696
+ bodyRow.appendChild(sidebar);
1697
+ bodyRow.appendChild(content);
1698
+ root.appendChild(bodyRow);
1699
+ const tooltip = createTooltipLayer();
1700
+ const unbindTip = bindTooltip(root, tooltip, (target) => resolveTipData(target));
1701
+ const views = /* @__PURE__ */ new Map();
1702
+ const containers = /* @__PURE__ */ new Map();
1703
+ const pluginViews = /* @__PURE__ */ new Map();
1704
+ const pluginSubs = [];
1705
+ const crashedPlugins = /* @__PURE__ */ new Map();
1706
+ for (const v of VIEWS) {
1707
+ const item = document.createElement("div");
1708
+ item.className = "pd-nav-item";
1709
+ item.dataset.view = v;
1710
+ const icon = document.createElement("span");
1711
+ icon.className = "pd-nav-icon";
1712
+ icon.textContent = VIEW_ICONS[v];
1713
+ const label = document.createElement("span");
1714
+ label.textContent = v;
1715
+ item.appendChild(icon);
1716
+ item.appendChild(label);
1717
+ sidebar.appendChild(item);
1718
+ const container = document.createElement("div");
1719
+ container.className = "pd-view";
1720
+ container.dataset.view = v;
1721
+ views.set(v, container);
1722
+ containers.set(v, container);
1723
+ content.appendChild(container);
1724
+ }
1725
+ function registerView(id, label, icon, render, pluginName) {
1726
+ if (views.has(id)) return;
1727
+ const item = document.createElement("div");
1728
+ item.className = "pd-nav-item";
1729
+ item.dataset.view = id;
1730
+ const ic = document.createElement("span");
1731
+ ic.className = "pd-nav-icon";
1732
+ ic.textContent = icon ?? "\u25B8";
1733
+ const lb = document.createElement("span");
1734
+ lb.textContent = label;
1735
+ item.appendChild(ic);
1736
+ item.appendChild(lb);
1737
+ sidebar.appendChild(item);
1738
+ const container = document.createElement("div");
1739
+ container.className = "pd-view";
1740
+ container.dataset.view = id;
1741
+ views.set(id, container);
1742
+ containers.set(id, container);
1743
+ pluginViews.set(id, { name: pluginName, render });
1744
+ content.appendChild(container);
1745
+ }
1746
+ const storage = options.storage ?? createMemoryStorage();
1747
+ const commands = createCommandRegistry();
1748
+ const registry = createPluginRegistry(options.plugins ?? []);
1749
+ function broadcastToPlugins(e) {
1750
+ for (const s of pluginSubs.slice()) s.cb(e);
1751
+ }
1752
+ function crashPlugin(name, err) {
1753
+ crashedPlugins.set(name, err instanceof Error ? err.message : String(err));
1754
+ for (let i = pluginSubs.length - 1; i >= 0; i--) {
1755
+ if (pluginSubs[i].name === name) pluginSubs.splice(i, 1);
1756
+ }
1757
+ }
1758
+ const zoom = createTimelineZoom(containers.get("timeline"), () => timeline.spans(), {
1759
+ onWindowChange: () => scheduleRender()
1760
+ });
1761
+ const timelineView = containers.get("timeline");
1762
+ let tlScrollTop = 0;
1763
+ let tlViewHeight = 300;
1764
+ const onTimelineScroll = () => {
1765
+ tlScrollTop = timelineView.scrollTop;
1766
+ scheduleRender();
1767
+ };
1768
+ timelineView.addEventListener("scroll", onTimelineScroll);
1769
+ function show(view) {
1770
+ for (const [k, el] of views) el.classList.toggle("pd-view-active", k === view);
1771
+ for (const el of Array.from(sidebar.children)) el.classList.toggle("pd-nav-active", el.dataset.view === view);
1772
+ if (view === "device") startMemorySampling();
1773
+ else stopMemorySampling();
1774
+ }
1775
+ sidebar.addEventListener("click", (e) => {
1776
+ const t = e.target.closest(".pd-nav-item");
1777
+ if (t?.dataset.view) show(t.dataset.view);
1778
+ });
1779
+ const timeline = createTimelineCollector();
1780
+ const flame = createFlamegraphCollector();
1781
+ const errors = createErrorDiagnoser();
1782
+ const navs = [];
1783
+ const inflightNav = /* @__PURE__ */ new Map();
1784
+ const storeSnapshots = /* @__PURE__ */ new Map();
1785
+ const storePatchHistory = /* @__PURE__ */ new Map();
1786
+ const storeSteps = [];
1787
+ let stepSeq = 0;
1788
+ let selectedStore = "";
1789
+ const componentNodes = /* @__PURE__ */ new Map();
1790
+ const componentDom = /* @__PURE__ */ new Map();
1791
+ let selectedComponent = 0;
1792
+ let travelIndex = null;
1793
+ const sessionEvents = [];
1794
+ const SESSION_EVENT_CAP = 2e4;
1795
+ function pushSessionEvent(e) {
1796
+ sessionEvents.push(e);
1797
+ if (sessionEvents.length > SESSION_EVENT_CAP) sessionEvents.shift();
1798
+ }
1799
+ const memorySamples = [];
1800
+ let memoryTimer = null;
1801
+ const MEMORY_SAMPLE_MS = 1e3;
1802
+ function sampleMemory() {
1803
+ const perf = performance;
1804
+ const mem = perf.memory;
1805
+ if (!mem) return;
1806
+ memorySamples.push({ t: Date.now(), used: mem.usedJSHeapSize, total: mem.totalJSHeapSize, limit: mem.jsHeapLimit });
1807
+ if (memorySamples.length > 60) memorySamples.shift();
1808
+ scheduleRender();
1809
+ }
1810
+ function startMemorySampling() {
1811
+ if (memoryTimer) return;
1812
+ memoryTimer = setInterval(sampleMemory, MEMORY_SAMPLE_MS);
1813
+ sampleMemory();
1814
+ }
1815
+ function stopMemorySampling() {
1816
+ if (memoryTimer) {
1817
+ clearInterval(memoryTimer);
1818
+ memoryTimer = null;
1819
+ }
1820
+ }
1821
+ function handleEvent(e) {
1822
+ if (e.source === "store" && e.payload && typeof e.payload === "object") {
1823
+ const p = e.payload;
1824
+ if (typeof p.id === "string" && !/action/i.test(e.name)) {
1825
+ const state = {};
1826
+ for (const k of Object.keys(p)) if (k !== "id") state[k] = p[k];
1827
+ const hist = storePatchHistory.get(p.id) ?? [];
1828
+ if (hist.some((h) => JSON.stringify(h.state) === JSON.stringify(state))) {
1829
+ storeSnapshots.set(p.id, state);
1830
+ if (!selectedStore) selectedStore = p.id;
1831
+ return true;
1832
+ }
1833
+ }
1834
+ }
1835
+ timeline.ingest(e);
1836
+ flame.ingest(e);
1837
+ errors.ingest(e);
1838
+ if (e.source === "router") {
1839
+ if (e.phase === "start" && /nav/i.test(e.name)) {
1840
+ const p = e.payload ?? {};
1841
+ const id = e.traceId ?? e.name + "-" + e.timestamp;
1842
+ inflightNav.set(id, {
1843
+ record: {
1844
+ id,
1845
+ from: { path: p.from?.path ?? "?" },
1846
+ to: { path: p.to?.path ?? e.name, query: p.to?.query },
1847
+ guards: [],
1848
+ durationMs: 0,
1849
+ traceId: e.traceId,
1850
+ timestamp: e.timestamp
1851
+ }
1852
+ });
1853
+ } else if (e.phase === "end" && /nav/i.test(e.name)) {
1854
+ const id = e.traceId ?? e.name + "-" + e.timestamp;
1855
+ const nav = inflightNav.get(id);
1856
+ if (nav) {
1857
+ nav.record.durationMs = Math.max(0, e.timestamp - nav.record.timestamp);
1858
+ navs.push(nav.record);
1859
+ inflightNav.delete(id);
1860
+ if (navs.length > 500) navs.shift();
1861
+ }
1862
+ } else if (/guard/i.test(e.name)) {
1863
+ let target = null;
1864
+ let targetTs = -Infinity;
1865
+ for (const nav of inflightNav.values()) {
1866
+ if (nav.record.timestamp > targetTs) {
1867
+ target = nav;
1868
+ targetTs = nav.record.timestamp;
1869
+ }
1870
+ }
1871
+ if (target) {
1872
+ let result = "next";
1873
+ if (/redirect/i.test(e.name)) result = "redirect";
1874
+ else if (/cancel/i.test(e.name)) result = "cancel";
1875
+ else if (/error/i.test(e.name)) result = "error";
1876
+ target.record.guards.push({ name: e.name, durationMs: 0, result });
1877
+ }
1878
+ }
1879
+ }
1880
+ if (e.source === "store") {
1881
+ if (e.payload && typeof e.payload === "object") {
1882
+ const p = e.payload;
1883
+ if (typeof p.id === "string") {
1884
+ const index = stepSeq++;
1885
+ const isAction = /action/i.test(e.name);
1886
+ if (isAction) {
1887
+ storeSteps.push({
1888
+ index,
1889
+ storeId: p.id,
1890
+ type: "action",
1891
+ name: String(p.name ?? "?"),
1892
+ payload: e.payload,
1893
+ timestamp: e.timestamp
1894
+ });
1895
+ if (storeSteps.length > 1e3) storeSteps.shift();
1896
+ } else {
1897
+ const state = {};
1898
+ for (const k of Object.keys(p)) if (k !== "id") state[k] = p[k];
1899
+ storeSnapshots.set(p.id, state);
1900
+ if (!selectedStore) selectedStore = p.id;
1901
+ const list = storePatchHistory.get(p.id) ?? [];
1902
+ list.push({ stepIndex: index, state });
1903
+ storePatchHistory.set(p.id, list);
1904
+ storeSteps.push({
1905
+ index,
1906
+ storeId: p.id,
1907
+ type: "patch",
1908
+ name: "patch",
1909
+ payload: e.payload,
1910
+ timestamp: e.timestamp
1911
+ });
1912
+ if (storeSteps.length > 1e3) storeSteps.shift();
1913
+ }
1914
+ travelIndex = null;
1915
+ }
1916
+ }
1917
+ }
1918
+ if (e.source === "component" && e.payload && typeof e.payload === "object") {
1919
+ const p = e.payload;
1920
+ if (typeof p.id === "number") {
1921
+ if (/inspect/i.test(e.name)) {
1922
+ componentDom.set(p.id, p.dom ?? { tag: "?", children: [] });
1923
+ } else if (/unmount/i.test(e.name)) {
1924
+ componentNodes.delete(p.id);
1925
+ componentDom.delete(p.id);
1926
+ } else if (/mount/i.test(e.name)) {
1927
+ const existing = componentNodes.get(p.id);
1928
+ componentNodes.set(p.id, {
1929
+ id: p.id,
1930
+ name: p.name ?? "Anonymous",
1931
+ parentId: p.parentId,
1932
+ ts: e.timestamp,
1933
+ count: (existing?.count ?? 0) + 1,
1934
+ // ★P1:mount 时刻 props/state 快照(序列化后 JSON-safe;详情面板展示)
1935
+ props: p.props,
1936
+ state: p.state
1937
+ });
1938
+ }
1939
+ }
1940
+ }
1941
+ pushSessionEvent(e);
1942
+ return true;
1943
+ }
1944
+ const fgControls = document.createElement("div");
1945
+ fgControls.className = "pd-fg-controls";
1946
+ const recBtn = document.createElement("button");
1947
+ recBtn.className = "pd-btn";
1948
+ recBtn.textContent = "\u5F00\u59CB\u5F55\u5236";
1949
+ let baseline = null;
1950
+ let compareEntries = [];
1951
+ let flameFocus = null;
1952
+ let flamePath = [];
1953
+ function findFlameNode(id) {
1954
+ const path = [];
1955
+ const walk = (n) => {
1956
+ path.push(n);
1957
+ if (n.id === id) return true;
1958
+ for (const c of n.children) if (walk(c)) return true;
1959
+ path.pop();
1960
+ return false;
1961
+ };
1962
+ for (const r of flame.roots()) if (walk(r)) return { node: path[path.length - 1], path: path.slice() };
1963
+ return null;
1964
+ }
1965
+ recBtn.addEventListener("click", () => {
1966
+ flameFocus = null;
1967
+ flamePath = [];
1968
+ if (flame.recording) {
1969
+ flame.stop();
1970
+ recBtn.textContent = "\u5F00\u59CB\u5F55\u5236";
1971
+ if (baseline) compareEntries = flame.compare(baseline);
1972
+ else compareEntries = [];
1973
+ baseline = flame.roots();
1974
+ } else {
1975
+ compareEntries = [];
1976
+ flame.start();
1977
+ recBtn.textContent = "\u505C\u6B62\u5F55\u5236";
1978
+ }
1979
+ rerender();
1980
+ });
1981
+ fgControls.appendChild(recBtn);
1982
+ function restoreAt(index) {
1983
+ const out = [];
1984
+ for (const [id, history] of storePatchHistory) {
1985
+ let state = null;
1986
+ for (const h of history) {
1987
+ if (h.stepIndex <= index) state = h.state;
1988
+ else break;
1989
+ }
1990
+ if (state) out.push({ id, state });
1991
+ }
1992
+ return out;
1993
+ }
1994
+ function setPathAt(obj, path, value) {
1995
+ if (path.length === 0) return false;
1996
+ let cur = obj;
1997
+ for (let i = 0; i < path.length - 1; i++) {
1998
+ const k = path[i];
1999
+ const next = cur[k];
2000
+ if (next === null || typeof next !== "object") return false;
2001
+ cur = next;
2002
+ }
2003
+ const last = path[path.length - 1];
2004
+ if (Array.isArray(cur)) {
2005
+ ;
2006
+ cur[Number(last)] = value;
2007
+ } else {
2008
+ ;
2009
+ cur[last] = value;
2010
+ }
2011
+ return true;
2012
+ }
2013
+ function editStoreValue(storeId, path, value) {
2014
+ const cur = storeSnapshots.get(storeId);
2015
+ if (!cur) return;
2016
+ const next = JSON.parse(JSON.stringify(cur));
2017
+ if (!setPathAt(next, path, value)) return;
2018
+ storeSnapshots.set(storeId, next);
2019
+ options.onApplyState?.([{ id: storeId, state: next }]);
2020
+ source.sendCommand?.("Proteus.restoreStores", { stores: [{ id: storeId, state: next }] });
2021
+ scheduleRender();
2022
+ }
2023
+ function exportSnapshot() {
2024
+ const stores = Array.from(storeSnapshots.entries()).map((kv) => ({ id: kv[0], state: kv[1] }));
2025
+ const json = serializeStoreSnapshot({ stores, steps: storeSteps.map((s) => ({ index: s.index, storeId: s.storeId, type: s.type, name: s.name, payload: s.payload, timestamp: s.timestamp })) });
2026
+ const sensitive = findSensitiveKeys(stores);
2027
+ if (sensitive.length) {
2028
+ const summary = sensitive.map((h) => `${h.storeId}: ${h.keys.join(", ")}`).join("\uFF1B");
2029
+ const ok = typeof window !== "undefined" && typeof window.confirm === "function" ? window.confirm(`\u5FEB\u7167\u5305\u542B\u654F\u611F\u5B57\u6BB5\uFF08\u5C06\u4E00\u5E76\u5BFC\u51FA\uFF09\uFF1A
2030
+ ${summary}
2031
+
2032
+ \u786E\u8BA4\u5BFC\u51FA\uFF1F`) : true;
2033
+ if (!ok) return "";
2034
+ }
2035
+ if (typeof URL === "undefined" || typeof URL.createObjectURL !== "function") return json;
2036
+ const blob = new Blob([json], { type: "application/json" });
2037
+ const url = URL.createObjectURL(blob);
2038
+ const a = document.createElement("a");
2039
+ a.href = url;
2040
+ a.download = "proteus-store-snapshot.json";
2041
+ a.click();
2042
+ URL.revokeObjectURL(url);
2043
+ return json;
2044
+ }
2045
+ function importSnapshot(json) {
2046
+ const parsed = parseStoreSnapshot(json);
2047
+ if (!parsed) return;
2048
+ storeSnapshots.clear();
2049
+ storePatchHistory.clear();
2050
+ storeSteps.length = 0;
2051
+ stepSeq = 0;
2052
+ selectedStore = "";
2053
+ for (const s of parsed.stores) {
2054
+ storeSnapshots.set(s.id, s.state);
2055
+ if (!selectedStore) selectedStore = s.id;
2056
+ }
2057
+ for (const st of parsed.steps) {
2058
+ const index = stepSeq++;
2059
+ storeSteps.push({ index, storeId: st.storeId, type: st.type, name: st.name, payload: st.payload, timestamp: st.timestamp });
2060
+ if (st.type === "patch" && st.payload && typeof st.payload === "object") {
2061
+ const p = st.payload;
2062
+ const state = {};
2063
+ for (const k of Object.keys(p)) if (k !== "id") state[k] = p[k];
2064
+ const list = storePatchHistory.get(st.storeId) ?? [];
2065
+ list.push({ stepIndex: index, state });
2066
+ storePatchHistory.set(st.storeId, list);
2067
+ }
2068
+ }
2069
+ scheduleRender();
2070
+ options.onApplyState?.(Array.from(storeSnapshots.entries()).map((kv) => ({ id: kv[0], state: kv[1] })));
2071
+ }
2072
+ function exportSession() {
2073
+ const json = serializeSession({
2074
+ events: sessionEvents,
2075
+ device: options.deviceInfo ? options.deviceInfo() : source.deviceInfo?.() ?? void 0,
2076
+ stores: Array.from(storeSnapshots.entries()).map((kv) => ({ id: kv[0], state: kv[1] })),
2077
+ steps: storeSteps.map((s) => ({ index: s.index, storeId: s.storeId, type: s.type, name: s.name, payload: s.payload, timestamp: s.timestamp }))
2078
+ });
2079
+ if (typeof URL === "undefined" || typeof URL.createObjectURL !== "function") return json;
2080
+ const blob = new Blob([json], { type: "application/json" });
2081
+ const url = URL.createObjectURL(blob);
2082
+ const a = document.createElement("a");
2083
+ a.href = url;
2084
+ a.download = "proteus-session.json";
2085
+ a.click();
2086
+ URL.revokeObjectURL(url);
2087
+ return json;
2088
+ }
2089
+ function importSession(json) {
2090
+ const parsed = parseSession(json);
2091
+ if (!parsed) return;
2092
+ timeline.clear();
2093
+ errors.clear();
2094
+ navs.length = 0;
2095
+ inflightNav.clear();
2096
+ storeSnapshots.clear();
2097
+ storePatchHistory.clear();
2098
+ storeSteps.length = 0;
2099
+ stepSeq = 0;
2100
+ travelIndex = null;
2101
+ selectedStore = "";
2102
+ componentNodes.clear();
2103
+ componentDom.clear();
2104
+ selectedComponent = 0;
2105
+ sessionEvents.length = 0;
2106
+ for (const e of parsed.events) handleEvent(e);
2107
+ options.onApplyState?.(Array.from(storeSnapshots.entries()).map((kv) => ({ id: kv[0], state: kv[1] })));
2108
+ scheduleRender();
2109
+ }
2110
+ function timeTravel(index) {
2111
+ travelIndex = index;
2112
+ options.onTimeTravel?.(index);
2113
+ const restore = restoreAt(index);
2114
+ options.onApplyState?.(restore);
2115
+ source.sendCommand?.("Proteus.restoreStores", { stores: restore });
2116
+ }
2117
+ function rerender() {
2118
+ tlViewHeight = timelineView.clientHeight || 300;
2119
+ renderTimeline(containers.get("timeline"), { spans: timeline.spans(), window: zoom.getWindow() ?? void 0, virtual: { scrollTop: tlScrollTop, viewHeight: tlViewHeight } });
2120
+ function fgData() {
2121
+ const data = { nodes: flame.roots(), compare: compareEntries.length ? compareEntries : void 0 };
2122
+ if (flameFocus && flamePath.length) {
2123
+ data.focus = flameFocus;
2124
+ data.breadcrumb = flamePath.map((n) => ({ id: n.id, name: n.source + "." + n.name }));
2125
+ }
2126
+ return data;
2127
+ }
2128
+ renderFlamegraph(containers.get("flamegraph"), fgData(), {
2129
+ // ★点击块 → 聚焦缩放(zoom 到该节点子树)
2130
+ onFocus: (id) => {
2131
+ const found = findFlameNode(id);
2132
+ if (found) {
2133
+ flameFocus = found.node;
2134
+ flamePath = found.path;
2135
+ scheduleRender();
2136
+ }
2137
+ },
2138
+ // ★返回上级(面包屑上一级;根 → 退出聚焦)
2139
+ onFocusUp: () => {
2140
+ if (flamePath.length > 1) {
2141
+ const parent = findFlameNode(flamePath[flamePath.length - 2].id);
2142
+ if (parent) {
2143
+ flameFocus = parent.node;
2144
+ flamePath = parent.path;
2145
+ } else {
2146
+ flameFocus = null;
2147
+ flamePath = [];
2148
+ }
2149
+ } else {
2150
+ flameFocus = null;
2151
+ flamePath = [];
2152
+ }
2153
+ scheduleRender();
2154
+ }
2155
+ });
2156
+ const flameContainer = containers.get("flamegraph");
2157
+ flameContainer.insertBefore(fgControls, flameContainer.firstChild);
2158
+ renderErrors(containers.get("errors"), { reports: errors.diagnose() });
2159
+ renderRoute(containers.get("route"), { records: navs });
2160
+ renderState(
2161
+ containers.get("state"),
2162
+ {
2163
+ snapshot: { version: 1, takenAt: Date.now(), stores: Array.from(storeSnapshots.entries()).map((kv) => ({ id: kv[0], state: kv[1] })) },
2164
+ // ★P0:真实 before/after(patch 步骤:该 store 补丁历史中前一条/本条状态;action 步骤无状态变更)
2165
+ steps: storeSteps.map((s) => {
2166
+ let before = {};
2167
+ let after = {};
2168
+ if (s.type === "patch") {
2169
+ const hist = storePatchHistory.get(s.storeId) ?? [];
2170
+ const idx = hist.findIndex((h) => h.stepIndex === s.index);
2171
+ if (idx >= 0) {
2172
+ after = hist[idx].state;
2173
+ if (idx > 0) before = hist[idx - 1].state;
2174
+ }
2175
+ }
2176
+ return { index: s.index, storeId: s.storeId, type: s.type, payload: s.payload, timestamp: s.timestamp, before, after };
2177
+ }),
2178
+ selectedStore,
2179
+ // ★滑块回放位置(rerender 后保持;新真实变更重置为最新)
2180
+ travelIndex: travelIndex ?? void 0
2181
+ },
2182
+ {
2183
+ onTimeTravel: timeTravel,
2184
+ onSelectStore: (id) => {
2185
+ selectedStore = id;
2186
+ scheduleRender();
2187
+ },
2188
+ onExport: exportSnapshot,
2189
+ onImport: importSnapshot,
2190
+ // ★M11 可观测性(M8.2):会话导出/导入(完整还原另一环境)
2191
+ onExportSession: exportSession,
2192
+ onImportSession: importSession,
2193
+ // ★双向调试:值编辑 → 面板快照 + 本地/远程双通道写回($patch 真实状态)
2194
+ onEditValue: editStoreValue
2195
+ }
2196
+ );
2197
+ const deviceInfo = options.deviceInfo ? options.deviceInfo() : source.deviceInfo?.() ?? void 0;
2198
+ renderDevice(containers.get("device"), { info: deviceInfo, memory: memorySamples.slice() });
2199
+ const ownershipData = options.ownershipData ? options.ownershipData() : source.ownership?.() ?? void 0;
2200
+ renderOwnership(containers.get("ownership"), ownershipData);
2201
+ renderComponents(
2202
+ containers.get("components"),
2203
+ { nodes: Array.from(componentNodes.values()), selectedId: selectedComponent || void 0, dom: selectedComponent ? componentDom.get(selectedComponent) : void 0 },
2204
+ {
2205
+ // ★P1:点击选中(同 id 再点取消选中);首次选中 → 页面元素高亮回调
2206
+ onSelect: (id) => {
2207
+ if (selectedComponent === id) {
2208
+ selectedComponent = 0;
2209
+ } else {
2210
+ selectedComponent = id;
2211
+ options.onSelectComponent?.(id);
2212
+ }
2213
+ scheduleRender();
2214
+ }
2215
+ }
2216
+ );
2217
+ renderPages(containers.get("pages"), resolvePagesData());
2218
+ renderGraph(containers.get("graph"), { routes: resolvePagesData().routes });
2219
+ for (const [id, pv] of pluginViews) {
2220
+ const container = containers.get(id);
2221
+ const err = crashedPlugins.get(pv.name);
2222
+ if (err !== void 0) {
2223
+ renderPluginCrash(container, pv.name, err);
2224
+ } else {
2225
+ try {
2226
+ pv.render(container);
2227
+ } catch (e) {
2228
+ crashPlugin(pv.name, e);
2229
+ renderPluginCrash(container, pv.name, e instanceof Error ? e.message : String(e));
2230
+ }
2231
+ }
2232
+ }
2233
+ }
2234
+ function renderPluginCrash(container, name, err) {
2235
+ container.replaceChildren();
2236
+ const card2 = document.createElement("div");
2237
+ card2.className = "pd-plugin-crash";
2238
+ const t = document.createElement("div");
2239
+ t.className = "pd-plugin-crash-title";
2240
+ t.textContent = "\u63D2\u4EF6\u5D29\u6E83\uFF1A" + name;
2241
+ const m = document.createElement("div");
2242
+ m.className = "pd-plugin-crash-msg";
2243
+ m.textContent = err;
2244
+ card2.appendChild(t);
2245
+ card2.appendChild(m);
2246
+ container.appendChild(card2);
2247
+ }
2248
+ let renderTimer = null;
2249
+ function scheduleRender() {
2250
+ if (renderTimer) return;
2251
+ renderTimer = setTimeout(() => {
2252
+ renderTimer = null;
2253
+ rerender();
2254
+ }, 16);
2255
+ }
2256
+ function resolvePagesData() {
2257
+ if (options.pages) return options.pages;
2258
+ const info = source.appInfo?.();
2259
+ return { routes: info?.routes ?? [] };
2260
+ }
2261
+ let statusConnected = false;
2262
+ const off = source.onEvent((e) => {
2263
+ if (!handleEvent(e)) {
2264
+ broadcastToPlugins(e);
2265
+ return;
2266
+ }
2267
+ broadcastToPlugins(e);
2268
+ scheduleRender();
2269
+ if (!statusConnected) {
2270
+ statusConnected = true;
2271
+ dot.classList.add("pd-dot-on");
2272
+ statusText.textContent = "\u5DF2\u8FDE\u63A5";
2273
+ }
2274
+ });
2275
+ const offStatus = source.onStatus?.((s) => {
2276
+ if (s === "connected" && !statusConnected) {
2277
+ statusConnected = true;
2278
+ dot.classList.add("pd-dot-on");
2279
+ statusText.textContent = "\u5DF2\u8FDE\u63A5";
2280
+ } else if (s === "closed") {
2281
+ statusConnected = false;
2282
+ dot.classList.remove("pd-dot-on");
2283
+ statusText.textContent = "\u5DF2\u65AD\u5F00";
2284
+ }
2285
+ });
2286
+ containers.get("flamegraph").insertBefore(fgControls, containers.get("flamegraph").firstChild);
2287
+ root.style.position = "relative";
2288
+ const paletteBtn = document.createElement("button");
2289
+ paletteBtn.className = "pd-btn pd-palette-btn";
2290
+ paletteBtn.textContent = "\u26A1";
2291
+ paletteBtn.title = "\u547D\u4EE4\u9762\u677F";
2292
+ const palette = document.createElement("div");
2293
+ palette.className = "pd-palette";
2294
+ palette.style.display = "none";
2295
+ function openPalette() {
2296
+ palette.replaceChildren();
2297
+ const ids = commands.list();
2298
+ if (ids.length === 0) {
2299
+ const empty = document.createElement("div");
2300
+ empty.className = "pd-palette-empty";
2301
+ empty.textContent = "\u6682\u65E0\u547D\u4EE4";
2302
+ palette.appendChild(empty);
2303
+ } else {
2304
+ for (const id of ids) {
2305
+ const item = document.createElement("div");
2306
+ item.className = "pd-palette-item";
2307
+ item.textContent = id;
2308
+ item.addEventListener("click", () => {
2309
+ commands.run(id);
2310
+ palette.style.display = "none";
2311
+ });
2312
+ palette.appendChild(item);
2313
+ }
2314
+ }
2315
+ palette.style.display = "block";
2316
+ }
2317
+ paletteBtn.addEventListener("click", () => {
2318
+ if (palette.style.display === "none") openPalette();
2319
+ else palette.style.display = "none";
2320
+ });
2321
+ header.insertBefore(paletteBtn, status);
2322
+ root.appendChild(palette);
2323
+ show("timeline");
2324
+ rerender();
2325
+ if ((options.plugins ?? []).length) {
2326
+ registry.activateAll((plugin) => {
2327
+ const pluginName = plugin.name;
2328
+ return {
2329
+ name: pluginName,
2330
+ bus: {
2331
+ on(cb) {
2332
+ const wrapped = (e) => {
2333
+ try {
2334
+ cb(e);
2335
+ } catch (err) {
2336
+ crashPlugin(pluginName, err);
2337
+ }
2338
+ };
2339
+ pluginSubs.push({ name: pluginName, cb: wrapped });
2340
+ return () => {
2341
+ for (let i = pluginSubs.length - 1; i >= 0; i--) {
2342
+ if (pluginSubs[i].cb === wrapped) pluginSubs.splice(i, 1);
2343
+ }
2344
+ };
2345
+ }
2346
+ },
2347
+ panel: {
2348
+ addView(id, opts) {
2349
+ registerView(id, opts.label, opts.icon, opts.render, pluginName);
2350
+ }
2351
+ },
2352
+ commands,
2353
+ storage
2354
+ };
2355
+ }).then(() => scheduleRender()).catch((err) => {
2356
+ console.error("[proteus-devtools] \u63D2\u4EF6\u6FC0\u6D3B\u5931\u8D25", err);
2357
+ statusText.textContent = "\u63D2\u4EF6\u6FC0\u6D3B\u5931\u8D25";
2358
+ });
2359
+ }
2360
+ return {
2361
+ destroy() {
2362
+ off();
2363
+ offStatus?.();
2364
+ unbindTip();
2365
+ tooltip.dispose();
2366
+ zoom.destroy();
2367
+ timelineView.removeEventListener("scroll", onTimelineScroll);
2368
+ stopMemorySampling();
2369
+ pluginSubs.length = 0;
2370
+ source.close();
2371
+ if (renderTimer) clearTimeout(renderTimer);
2372
+ root.replaceChildren();
2373
+ },
2374
+ show,
2375
+ exportSnapshot,
2376
+ importSnapshot,
2377
+ exportSession,
2378
+ importSession
2379
+ };
2380
+ }
2381
+
2382
+ // src/source.ts
2383
+ function createTraceBusSource(bus) {
2384
+ return {
2385
+ onEvent(cb) {
2386
+ return bus.on(cb);
2387
+ },
2388
+ // 本地直连:进程内总线,状态恒已连接
2389
+ onStatus(cb) {
2390
+ cb("connected");
2391
+ return () => {
2392
+ };
2393
+ },
2394
+ close() {
2395
+ }
2396
+ };
2397
+ }
2398
+ function createDevtoolsWsSource(url, createSocket) {
2399
+ const makeSocket = createSocket ?? ((u) => new WebSocket(u));
2400
+ const handlers = [];
2401
+ const statusHandlers = [];
2402
+ let ws = null;
2403
+ let closed = false;
2404
+ let seq = 0;
2405
+ let reconnectTimer = null;
2406
+ let appInfoCache;
2407
+ let status = "connecting";
2408
+ let enableId = null;
2409
+ let appInfoId = null;
2410
+ let enableAcked = false;
2411
+ let retryTimer = null;
2412
+ let deviceInfoId = null;
2413
+ let deviceInfoCache;
2414
+ let ownershipId = null;
2415
+ let ownershipCache;
2416
+ function setStatus(s) {
2417
+ status = s;
2418
+ for (const h of statusHandlers) h(s);
2419
+ }
2420
+ function sendCommands(sock) {
2421
+ enableId = ++seq;
2422
+ enableAcked = false;
2423
+ sock.send(JSON.stringify({ id: enableId, method: "Proteus.enable" }));
2424
+ appInfoId = ++seq;
2425
+ sock.send(JSON.stringify({ id: appInfoId, method: "Proteus.appInfo" }));
2426
+ deviceInfoId = ++seq;
2427
+ sock.send(JSON.stringify({ id: deviceInfoId, method: "Proteus.deviceInfo" }));
2428
+ ownershipId = ++seq;
2429
+ sock.send(JSON.stringify({ id: ownershipId, method: "Proteus.ownership" }));
2430
+ }
2431
+ function sendCommand(method, params) {
2432
+ if (!ws || ws.readyState !== WebSocket.OPEN) return;
2433
+ ws.send(JSON.stringify({ id: ++seq, method, params }));
2434
+ }
2435
+ function startRetry() {
2436
+ if (retryTimer) return;
2437
+ retryTimer = setInterval(() => {
2438
+ if (closed || !ws || ws.readyState !== WebSocket.OPEN || enableAcked) return;
2439
+ sendCommands(ws);
2440
+ }, 2e3);
2441
+ }
2442
+ function connect() {
2443
+ if (closed) return;
2444
+ try {
2445
+ ws = makeSocket(url);
2446
+ } catch {
2447
+ scheduleReconnect();
2448
+ return;
2449
+ }
2450
+ const sock = ws;
2451
+ sock.onopen = () => {
2452
+ setStatus("connected");
2453
+ sendCommands(sock);
2454
+ startRetry();
2455
+ };
2456
+ sock.onmessage = (ev) => {
2457
+ let msg;
2458
+ try {
2459
+ msg = JSON.parse(String(ev.data));
2460
+ } catch {
2461
+ return;
2462
+ }
2463
+ if (!msg || typeof msg !== "object") return;
2464
+ if (msg.id === enableId && msg.result !== void 0) {
2465
+ enableAcked = true;
2466
+ return;
2467
+ }
2468
+ if (msg.id === appInfoId && msg.result !== void 0) {
2469
+ appInfoCache = msg.result;
2470
+ return;
2471
+ }
2472
+ if (msg.id === deviceInfoId && msg.result !== void 0) {
2473
+ deviceInfoCache = msg.result;
2474
+ return;
2475
+ }
2476
+ if (msg.id === ownershipId && msg.result !== void 0) {
2477
+ ownershipCache = msg.result;
2478
+ return;
2479
+ }
2480
+ if (msg.method !== "Proteus.event") return;
2481
+ const p = msg.params ?? {};
2482
+ const source = p.source;
2483
+ const name = p.name;
2484
+ if (!source || !name) return;
2485
+ const event = {
2486
+ source,
2487
+ phase: p.phase ?? "point",
2488
+ name,
2489
+ payload: p.payload,
2490
+ timestamp: p.timestamp ?? Date.now(),
2491
+ traceId: p.traceId
2492
+ };
2493
+ for (const h of handlers) h(event);
2494
+ };
2495
+ sock.onclose = () => {
2496
+ setStatus("closed");
2497
+ if (!closed) scheduleReconnect();
2498
+ };
2499
+ }
2500
+ function scheduleReconnect() {
2501
+ if (closed || reconnectTimer) return;
2502
+ reconnectTimer = setTimeout(() => {
2503
+ reconnectTimer = null;
2504
+ connect();
2505
+ }, 1e3);
2506
+ }
2507
+ connect();
2508
+ return {
2509
+ onEvent(cb) {
2510
+ handlers.push(cb);
2511
+ return () => {
2512
+ const i = handlers.indexOf(cb);
2513
+ if (i >= 0) handlers.splice(i, 1);
2514
+ };
2515
+ },
2516
+ onStatus(cb) {
2517
+ statusHandlers.push(cb);
2518
+ cb(status);
2519
+ return () => {
2520
+ const i = statusHandlers.indexOf(cb);
2521
+ if (i >= 0) statusHandlers.splice(i, 1);
2522
+ };
2523
+ },
2524
+ close() {
2525
+ closed = true;
2526
+ if (reconnectTimer) {
2527
+ clearTimeout(reconnectTimer);
2528
+ reconnectTimer = null;
2529
+ }
2530
+ if (retryTimer) {
2531
+ clearInterval(retryTimer);
2532
+ retryTimer = null;
2533
+ }
2534
+ ws?.close();
2535
+ ws = null;
2536
+ },
2537
+ appInfo() {
2538
+ return appInfoCache;
2539
+ },
2540
+ /** ★M8:设备信息(环境/能力;Proteus.deviceInfo 命令响应缓存;未确认前 undefined) */
2541
+ deviceInfo() {
2542
+ return deviceInfoCache;
2543
+ },
2544
+ /** ★G-43 B4:所有权视图数据(Proteus.ownership 命令响应缓存;未确认前 undefined) */
2545
+ ownership() {
2546
+ return ownershipCache;
2547
+ },
2548
+ sendCommand
2549
+ };
2550
+ }
2551
+
2552
+ // src/ws-bridge.ts
2553
+ function createTraceBusWsBridge(bus, options) {
2554
+ const ws = new WebSocket(options.url);
2555
+ function sendEvent(e) {
2556
+ if (ws.readyState !== WebSocket.OPEN) return;
2557
+ ws.send(
2558
+ JSON.stringify({
2559
+ method: "Proteus.event",
2560
+ params: { source: e.source, phase: e.phase, name: e.name, payload: e.payload, timestamp: e.timestamp, traceId: e.traceId }
2561
+ })
2562
+ );
2563
+ }
2564
+ const off = bus.on((e) => sendEvent(e));
2565
+ ws.onmessage = (ev) => {
2566
+ let msg = null;
2567
+ try {
2568
+ msg = JSON.parse(String(ev.data));
2569
+ } catch {
2570
+ return;
2571
+ }
2572
+ if (!msg || typeof msg !== "object") return;
2573
+ if (msg.method === "Proteus.enable") {
2574
+ for (const e of bus.flush()) sendEvent(e);
2575
+ ws.send(JSON.stringify({ id: msg.id, result: {} }));
2576
+ } else if (msg.method === "Proteus.appInfo") {
2577
+ ws.send(JSON.stringify({ id: msg.id, result: options.appInfo ? options.appInfo() : {} }));
2578
+ } else if (msg.method === "Proteus.deviceInfo") {
2579
+ ws.send(JSON.stringify({ id: msg.id, result: options.deviceInfo ? options.deviceInfo() : {} }));
2580
+ } else if (msg.method === "Proteus.ownership") {
2581
+ ws.send(JSON.stringify({ id: msg.id, result: options.ownership ? options.ownership() : {} }));
2582
+ } else if (msg.method === "Proteus.restoreStores") {
2583
+ const params = msg.params;
2584
+ if (options.onRestoreStores && Array.isArray(params?.stores)) {
2585
+ options.onRestoreStores(params.stores);
2586
+ }
2587
+ ws.send(JSON.stringify({ id: msg.id, result: {} }));
2588
+ }
2589
+ };
2590
+ return {
2591
+ close() {
2592
+ off();
2593
+ ws.close();
2594
+ }
2595
+ };
2596
+ }
2597
+
2598
+ // src/vue-devtools.ts
2599
+ var LAYER_ID = "proteus";
2600
+ function toTimelineEvent(e) {
2601
+ return {
2602
+ time: e.timestamp,
2603
+ title: e.source + "." + e.name,
2604
+ subtitle: e.phase,
2605
+ data: {
2606
+ source: e.source,
2607
+ name: e.name,
2608
+ phase: e.phase,
2609
+ traceId: e.traceId,
2610
+ payload: e.payload
2611
+ },
2612
+ groupId: e.traceId ?? e.source
2613
+ };
2614
+ }
2615
+ function installProteusTimeline(api, options) {
2616
+ api.addTimelineLayer({ id: LAYER_ID, label: "Proteus", color: options.color ?? 11101205 });
2617
+ const off = options.source.onEvent((e) => {
2618
+ api.addTimelineEvent({ layerId: LAYER_ID, event: toTimelineEvent(e) });
2619
+ });
2620
+ return {
2621
+ dispose: off,
2622
+ get layerId() {
2623
+ return LAYER_ID;
2624
+ }
2625
+ };
2626
+ }
2627
+ function pathToPatch(path, value, groupKey) {
2628
+ let p = path;
2629
+ if (groupKey && p[0] === groupKey) p = p.slice(1);
2630
+ const root = {};
2631
+ let cur = root;
2632
+ for (let i = 0; i < p.length - 1; i++) {
2633
+ const next = {};
2634
+ cur[p[i]] = next;
2635
+ cur = next;
2636
+ }
2637
+ cur[p[p.length - 1]] = value;
2638
+ return root;
2639
+ }
2640
+ function configToStateRows(config) {
2641
+ const keys = Object.keys(config);
2642
+ return keys.length ? keys.map((k) => ({ key: k, value: config[k] })) : [{ key: "(empty)", value: {} }];
2643
+ }
2644
+ var PROTEUS_DEVTOOLS_PLUGIN_DESCRIPTOR = {
2645
+ id: "proteus",
2646
+ label: "Proteus",
2647
+ /** ★触发 fallback 的占位 logo(根相对路径:扩展/独立/dev server 下 404 或图片解码失败 → img error)
2648
+ * 不能传 undefined(三个 inspector 全默认图标),也不能传真实图片 URL(会直接显示图片而非字典图标) */
2649
+ logo: "/__proteus-inspector-icons__.svg"
2650
+ };
2651
+ var APP_CONFIG_INSPECTOR = "proteus-app-config";
2652
+ var STYLE_SAFETY_INSPECTOR = "proteus-style-safety";
2653
+ var ROUTER_INSPECTOR = "proteus-router";
2654
+ var TAG_STYLE = { textColor: 16777215, backgroundColor: 1735416 };
2655
+ function buildRouterTree(routes, currentRoute) {
2656
+ const byName = new Map(routes.map((r) => [r.name, r]));
2657
+ const childrenOf = /* @__PURE__ */ new Map();
2658
+ const roots = [];
2659
+ for (const r of routes) {
2660
+ if (r.parent && byName.has(r.parent)) {
2661
+ const list = childrenOf.get(r.parent) ?? [];
2662
+ list.push(r);
2663
+ childrenOf.set(r.parent, list);
2664
+ } else {
2665
+ roots.push(r);
2666
+ }
2667
+ }
2668
+ const CURRENT_STYLE = { textColor: 16777215, backgroundColor: 508256 };
2669
+ const toNode = (r) => ({
2670
+ id: r.name,
2671
+ label: r.meta?.title ?? r.name,
2672
+ // ★当前路由高亮(path 匹配 → 绿色「当前」tag)
2673
+ tags: [
2674
+ ...r.path === currentRoute ? [{ label: "\u5F53\u524D", ...CURRENT_STYLE }] : [],
2675
+ { label: r.path, ...TAG_STYLE }
2676
+ ],
2677
+ children: (childrenOf.get(r.name) ?? []).map(toNode)
2678
+ });
2679
+ return roots.map(toNode);
2680
+ }
2681
+ function installProteusInspectors(api, options = {}) {
2682
+ api.addInspector({ id: APP_CONFIG_INSPECTOR, label: "App Config", icon: "settings" });
2683
+ api.on.getInspectorTree((payload) => {
2684
+ if (payload.inspectorId !== APP_CONFIG_INSPECTOR) return;
2685
+ payload.rootNodes = [{ id: "root", label: "App Config" }];
2686
+ });
2687
+ api.on.getInspectorState((payload) => {
2688
+ if (payload.inspectorId !== APP_CONFIG_INSPECTOR) return;
2689
+ payload.state = { resolved: configToStateRows(options.getConfig ? options.getConfig() : {}) };
2690
+ });
2691
+ if (options.setConfig) {
2692
+ api.on.editInspectorState((payload) => {
2693
+ if (payload.inspectorId !== APP_CONFIG_INSPECTOR) return;
2694
+ options.setConfig?.(pathToPatch(payload.path, payload.state.value, "resolved"));
2695
+ });
2696
+ }
2697
+ if (options.getStyleSafetyRecords) {
2698
+ api.addInspector({ id: STYLE_SAFETY_INSPECTOR, label: "Style Safety", icon: "gpp-good" });
2699
+ api.on.getInspectorTree((payload) => {
2700
+ if (payload.inspectorId !== STYLE_SAFETY_INSPECTOR) return;
2701
+ payload.rootNodes = [{ id: "root", label: "Style Safety" }];
2702
+ });
2703
+ api.on.getInspectorState((payload) => {
2704
+ if (payload.inspectorId !== STYLE_SAFETY_INSPECTOR) return;
2705
+ const records = options.getStyleSafetyRecords();
2706
+ payload.state = {
2707
+ rejected: records.map((r, i) => ({ key: r.prop || "#" + i, value: { value: r.value, reason: r.reason, ts: r.ts } }))
2708
+ };
2709
+ });
2710
+ }
2711
+ if (options.pages) {
2712
+ api.addInspector({ id: ROUTER_INSPECTOR, label: "Router", icon: "route" });
2713
+ api.on.getInspectorTree((payload) => {
2714
+ if (payload.inspectorId !== ROUTER_INSPECTOR) return;
2715
+ const routerState = options.getRouterState?.();
2716
+ const nodes = buildRouterTree(options.pages?.routes ?? [], routerState?.currentRoute);
2717
+ if (routerState) {
2718
+ const recordNodes = routerState.records.slice(-20).reverse().map((r) => ({
2719
+ id: "rec-" + r.timestamp,
2720
+ // ★带参导航显示 query(如 pages/user/profile?id=1)
2721
+ label: `${r.from} \u2192 ${r.to}${r.query && Object.keys(r.query).length ? "?" + new URLSearchParams(r.query).toString() : ""}`,
2722
+ tags: [{ label: r.durationMs + "ms", ...TAG_STYLE }]
2723
+ }));
2724
+ nodes.unshift({
2725
+ id: "proteus-records",
2726
+ label: `\u5BFC\u822A\u8BB0\u5F55 (${routerState.records.length})`,
2727
+ tags: routerState.currentRoute ? [{ label: "\u5F53\u524D: " + routerState.currentRoute, ...TAG_STYLE }] : void 0,
2728
+ children: recordNodes
2729
+ });
2730
+ }
2731
+ payload.rootNodes = nodes;
2732
+ });
2733
+ api.on.getInspectorState((payload) => {
2734
+ if (payload.inspectorId !== ROUTER_INSPECTOR) return;
2735
+ if (payload.nodeId === "proteus-records") {
2736
+ const state = options.getRouterState?.();
2737
+ payload.state = {
2738
+ \u5BFC\u822A\u8BB0\u5F55: [
2739
+ { key: "currentRoute", value: state?.currentRoute ?? "\u2014" },
2740
+ { key: "records", value: (state?.records ?? []).slice(-50).reverse() }
2741
+ ]
2742
+ };
2743
+ return;
2744
+ }
2745
+ if (payload.nodeId.startsWith("rec-")) {
2746
+ const rec = (options.getRouterState?.()?.records ?? []).find((r) => "rec-" + r.timestamp === payload.nodeId);
2747
+ payload.state = rec ? {
2748
+ \u5BFC\u822A\u72B6\u6001: [
2749
+ { key: "from", value: rec.from },
2750
+ { key: "to", value: rec.to },
2751
+ { key: "query", value: rec.query ?? {} },
2752
+ { key: "durationMs", value: rec.durationMs },
2753
+ { key: "timestamp", value: rec.timestamp },
2754
+ { key: "traceId", value: rec.traceId ?? "\u2014" },
2755
+ { key: "guards", value: rec.guards }
2756
+ ]
2757
+ } : {};
2758
+ return;
2759
+ }
2760
+ const route = (options.pages?.routes ?? []).find((r) => r.name === payload.nodeId);
2761
+ if (!route) {
2762
+ payload.state = {};
2763
+ return;
2764
+ }
2765
+ payload.state = {
2766
+ \u8DEF\u7531: [
2767
+ { key: "path", value: route.path },
2768
+ { key: "parent", value: route.parent ?? "\u2014" },
2769
+ { key: "subPackage", value: route.subPackage ?? "\u2014" },
2770
+ { key: "meta", value: route.meta ?? {} }
2771
+ ]
2772
+ };
2773
+ });
2774
+ }
2775
+ return {
2776
+ dispose() {
2777
+ }
2778
+ };
2779
+ }
2780
+
2781
+ // src/ownership-info.ts
2782
+ import {
2783
+ createOwnershipCounters,
2784
+ createOwnershipHistory,
2785
+ diagnoseOwnershipIssues,
2786
+ buildOwnershipTimeline,
2787
+ getProteusOwnershipGraph
2788
+ } from "@proteus-vue/render-backend";
2789
+ var TIMELINE_LIMIT = 200;
2790
+ function createOwnershipTracer(options = {}) {
2791
+ const graph = options.graph ?? getProteusOwnershipGraph();
2792
+ const counters = createOwnershipCounters(graph);
2793
+ const history = options.history ?? createOwnershipHistory(graph);
2794
+ let disposed = false;
2795
+ function collect() {
2796
+ if (disposed) {
2797
+ return {
2798
+ summary: { alive: 0, total: 0, bytesAlive: 0, byType: {} },
2799
+ diagnosis: { orphans: [], leaks: [], longBorrows: [], crossPageRefs: [] },
2800
+ resources: [],
2801
+ timeline: { events: [], unpairedIds: [], truncated: false }
2802
+ };
2803
+ }
2804
+ const diagnosis = diagnoseOwnershipIssues(graph);
2805
+ const seenAllocIds = /* @__PURE__ */ new Set();
2806
+ for (const r of history.records) {
2807
+ if (r.kind === "alloc" && r.id) seenAllocIds.add(r.id);
2808
+ }
2809
+ const merged = [...history.records];
2810
+ for (const n of graph.nodes.values()) {
2811
+ if (!seenAllocIds.has(n.id)) {
2812
+ merged.push({ ts: n.createdAt, kind: "alloc", id: n.id, owner: n.owner, type: n.type, byteSize: n.byteSize, sourceLocation: n.sourceLocation });
2813
+ }
2814
+ }
2815
+ merged.sort((a, b) => a.ts - b.ts);
2816
+ const tl = buildOwnershipTimeline({ records: merged }, graph);
2817
+ const truncated = tl.events.length > TIMELINE_LIMIT;
2818
+ const events = truncated ? tl.events.slice(-TIMELINE_LIMIT) : tl.events;
2819
+ const borrowedBy = /* @__PURE__ */ new Map();
2820
+ for (const e of graph.edges) {
2821
+ if (e.kind !== "borrows") continue;
2822
+ const list = borrowedBy.get(e.to) ?? [];
2823
+ list.push(e.from);
2824
+ borrowedBy.set(e.to, list);
2825
+ }
2826
+ const byOwner = /* @__PURE__ */ new Map();
2827
+ for (const n of graph.nodes.values()) {
2828
+ if (n.state !== "alive") continue;
2829
+ const key = n.owner ?? "\uFF08\u65E0\u4E3B\uFF09";
2830
+ const list = byOwner.get(key) ?? [];
2831
+ list.push({ id: n.id, type: n.type, byteSize: n.byteSize, owner: n.owner, state: n.state, sourceLocation: n.sourceLocation, borrowedBy: borrowedBy.get(n.id) ?? [] });
2832
+ byOwner.set(key, list);
2833
+ }
2834
+ return {
2835
+ summary: { alive: counters.alive, total: counters.total, bytesAlive: counters.bytesAlive, byType: counters.byType },
2836
+ diagnosis: {
2837
+ orphans: diagnosis.orphans.map((o) => ({ id: o.id, type: o.type, byteSize: o.byteSize, sourceLocation: o.sourceLocation })),
2838
+ leaks: diagnosis.leaks.map((l) => ({ resourceId: l.resourceId, type: l.type, byteSize: l.byteSize, sourceLocation: l.sourceLocation, referenceChain: l.referenceChain })),
2839
+ longBorrows: diagnosis.longBorrows.map((b) => ({ resourceId: b.resourceId, borrowedBy: b.borrowedBy, owner: b.owner })),
2840
+ crossPageRefs: diagnosis.crossPageRefs.map((c) => ({ resourceId: c.resourceId, owner: c.owner, heldBy: c.heldBy }))
2841
+ },
2842
+ resources: [...byOwner.entries()].map(([owner, items]) => ({ owner, items })),
2843
+ timeline: {
2844
+ events: events.map((e) => ({ ts: e.ts, kind: e.kind, id: e.id, owner: e.owner, type: e.type, byteSize: e.byteSize, sourceLocation: e.sourceLocation, from: e.from, to: e.to, matchedAllocId: e.matchedAlloc?.id })),
2845
+ unpairedIds: tl.unpairedAllocs.map((a) => a.id ?? ""),
2846
+ truncated
2847
+ }
2848
+ };
2849
+ }
2850
+ return {
2851
+ collect,
2852
+ dispose() {
2853
+ disposed = true;
2854
+ history.dispose();
2855
+ }
2856
+ };
2857
+ }
2858
+
2859
+ // src/component-trace.ts
2860
+ import { serializeState } from "@proteus-vue/devtools-runtime";
2861
+ function buildDomTree(el, depth = 0) {
2862
+ if (depth > 4) return null;
2863
+ const node = { tag: el.tagName.toLowerCase(), children: [] };
2864
+ if (el.id) node.id = el.id;
2865
+ const cls = Array.from(el.classList);
2866
+ if (cls.length) node.cls = cls.slice(0, 10);
2867
+ let count = 0;
2868
+ for (const child of Array.from(el.children)) {
2869
+ if (count >= 20) break;
2870
+ const sub = buildDomTree(child, depth + 1);
2871
+ if (sub) {
2872
+ node.children.push(sub);
2873
+ count++;
2874
+ }
2875
+ }
2876
+ return node;
2877
+ }
2878
+ function installComponentTrace(app, bus) {
2879
+ const ids = /* @__PURE__ */ new WeakMap();
2880
+ const elements = /* @__PURE__ */ new Map();
2881
+ let seq = 0;
2882
+ function getId(instance) {
2883
+ const id = ids.get(instance);
2884
+ if (id !== void 0) return id;
2885
+ const next = ++seq;
2886
+ ids.set(instance, next);
2887
+ return next;
2888
+ }
2889
+ function getRootEl(self) {
2890
+ const el = self.$el;
2891
+ if (!el || typeof el !== "object") return null;
2892
+ const node = el;
2893
+ if (node.nodeType === 1) return el;
2894
+ return node.firstElementChild ?? null;
2895
+ }
2896
+ function snapshot(self) {
2897
+ const out = {};
2898
+ if (self.$props && typeof self.$props === "object") out.props = serializeState(self.$props);
2899
+ const data = self.$data && typeof self.$data === "object" ? self.$data : void 0;
2900
+ const setupState = self.$?.setupState;
2901
+ if (data && Object.keys(data).length) {
2902
+ out.state = serializeState(data);
2903
+ } else if (setupState && typeof setupState === "object" && Object.keys(setupState).length) {
2904
+ out.state = serializeState(setupState);
2905
+ }
2906
+ return out;
2907
+ }
2908
+ app.mixin({
2909
+ mounted() {
2910
+ const self = this;
2911
+ const parentId = self.$parent ? getId(self.$parent) : void 0;
2912
+ const name = self.$options?.name ?? self.$options?.__name ?? "Anonymous";
2913
+ const id = getId(self);
2914
+ const el = getRootEl(self);
2915
+ elements.set(id, el);
2916
+ bus.emit("component", "point", "component.mount", { id, name, parentId, ...snapshot(self) }, "comp-" + id);
2917
+ },
2918
+ unmounted() {
2919
+ const id = getId(this);
2920
+ elements.delete(id);
2921
+ bus.emit("component", "point", "component.unmount", { id }, "comp-" + id);
2922
+ }
2923
+ });
2924
+ return {
2925
+ dispose() {
2926
+ },
2927
+ getElement(id) {
2928
+ return elements.get(id) ?? null;
2929
+ }
2930
+ };
2931
+ }
2932
+
2933
+ // src/install.ts
2934
+ import { getProteusTraceBus, createStoreTracer } from "@proteus-vue/devtools-runtime";
2935
+ import { setupDevtoolsPlugin } from "@vue/devtools-api";
2936
+ import { setCapabilityTraceBus, globalRegistry } from "@proteus-vue/capabilities";
2937
+
2938
+ // src/device-info.ts
2939
+ import { detectPlatform } from "@proteus-vue/capabilities";
2940
+ function detectRuntimePlatform() {
2941
+ if (typeof window !== "undefined" && typeof document !== "undefined") return "web";
2942
+ return detectPlatform();
2943
+ }
2944
+ function detectBrowserVersion(ua) {
2945
+ if (!ua) return void 0;
2946
+ const m = ua.match(/(?:Chrome|Firefox|Safari|Edg)\/([\d.]+)/);
2947
+ if (!m) return void 0;
2948
+ const name = /Firefox/.test(ua) ? "Firefox" : /Edg\//.test(ua) ? "Edge" : /Chrome/.test(ua) ? "Chrome" : "Safari";
2949
+ return name + " " + m[1];
2950
+ }
2951
+ function detectMpLibVersion(wxGlobal) {
2952
+ const g = wxGlobal;
2953
+ try {
2954
+ return g?.getAppBaseInfo?.()?.SDKVersion ?? g?.getSystemInfoSync?.()?.SDKVersion;
2955
+ } catch {
2956
+ return void 0;
2957
+ }
2958
+ }
2959
+
2960
+ // src/install.ts
2961
+ var panelMounted = false;
2962
+ var panel = null;
2963
+ var PANEL_POS_KEY = "proteus-devtools-panel-pos";
2964
+ function loadPanelPos() {
2965
+ try {
2966
+ const raw = localStorage.getItem(PANEL_POS_KEY);
2967
+ if (!raw) return null;
2968
+ const p = JSON.parse(raw);
2969
+ if (typeof p?.left === "number" && typeof p?.top === "number") return { left: p.left, top: p.top };
2970
+ } catch {
2971
+ }
2972
+ return null;
2973
+ }
2974
+ function savePanelPos(left, top) {
2975
+ try {
2976
+ localStorage.setItem(PANEL_POS_KEY, JSON.stringify({ left, top }));
2977
+ } catch {
2978
+ }
2979
+ }
2980
+ function makeFloatingDraggable(host, handle) {
2981
+ handle.classList.add("pd-floating-draggable");
2982
+ let startX = 0;
2983
+ let startY = 0;
2984
+ let origLeft = 0;
2985
+ let origTop = 0;
2986
+ handle.addEventListener("mousedown", (e) => {
2987
+ if (e.target.closest("button, input, a, .pd-palette-btn")) return;
2988
+ startX = e.clientX;
2989
+ startY = e.clientY;
2990
+ const rect = host.getBoundingClientRect();
2991
+ origLeft = rect.left;
2992
+ origTop = rect.top;
2993
+ host.style.left = origLeft + "px";
2994
+ host.style.top = origTop + "px";
2995
+ host.style.right = "auto";
2996
+ host.style.bottom = "auto";
2997
+ const onMove = (ev) => {
2998
+ const maxLeft = Math.max(0, (typeof window !== "undefined" ? window.innerWidth : 1024) - 160);
2999
+ const maxTop = Math.max(0, (typeof window !== "undefined" ? window.innerHeight : 768) - 80);
3000
+ const left = Math.max(0, Math.min(origLeft + (ev.clientX - startX), maxLeft));
3001
+ const top = Math.max(0, Math.min(origTop + (ev.clientY - startY), maxTop));
3002
+ host.style.left = left + "px";
3003
+ host.style.top = top + "px";
3004
+ };
3005
+ const onUp = () => {
3006
+ document.removeEventListener("mousemove", onMove);
3007
+ document.removeEventListener("mouseup", onUp);
3008
+ savePanelPos(parseFloat(host.style.left) || 0, parseFloat(host.style.top) || 0);
3009
+ };
3010
+ document.addEventListener("mousemove", onMove);
3011
+ document.addEventListener("mouseup", onUp);
3012
+ e.preventDefault();
3013
+ });
3014
+ }
3015
+ function mountFloatingPanel(bus, pages, applyState, selectComponent, pinia, deviceInfo, ownershipData) {
3016
+ if (panelMounted) return;
3017
+ panelMounted = true;
3018
+ const btn = document.createElement("button");
3019
+ btn.className = "pd-floating-toggle";
3020
+ btn.textContent = "\u25C8";
3021
+ btn.title = "Proteus DevTools";
3022
+ const host = document.createElement("div");
3023
+ host.className = "pd-floating-host";
3024
+ host.style.display = "none";
3025
+ document.body.appendChild(btn);
3026
+ document.body.appendChild(host);
3027
+ btn.addEventListener("click", () => {
3028
+ if (host.style.display !== "none") {
3029
+ host.style.display = "none";
3030
+ return;
3031
+ }
3032
+ const saved = loadPanelPos();
3033
+ if (saved) {
3034
+ host.style.left = saved.left + "px";
3035
+ host.style.top = saved.top + "px";
3036
+ host.style.right = "auto";
3037
+ host.style.bottom = "auto";
3038
+ }
3039
+ host.style.display = "block";
3040
+ if (!panel) {
3041
+ panel = createDevtoolsPanel(host, {
3042
+ source: createTraceBusSource(bus),
3043
+ pages,
3044
+ // ★M8 设备面板:环境/能力信息(navigator/screen + 能力注册表快照)
3045
+ deviceInfo,
3046
+ // ★G-43 B4 所有权面板:视图数据(tracer 采集闭包)
3047
+ ownershipData,
3048
+ // ★P0:时间旅行回放 / 导入快照恢复 → 逐 store $patch 写回(结构类型零硬依赖;无 pinia → 仅面板内展示)
3049
+ onApplyState: applyState,
3050
+ // ★P1:组件视图选中 → 页面元素高亮(scrollIntoView + 描边闪烁)
3051
+ onSelectComponent: selectComponent
3052
+ });
3053
+ const header = host.querySelector(".pd-header");
3054
+ if (header) makeFloatingDraggable(host, header);
3055
+ if (pinia) {
3056
+ for (const entry of pinia._s) {
3057
+ const id = entry[0];
3058
+ const state = entry[1].$state;
3059
+ if (!state) continue;
3060
+ const snapshot = { id };
3061
+ for (const k of Object.keys(state)) if (k !== "$id") snapshot[k] = state[k];
3062
+ bus.emit("store", "point", "store.patch", snapshot, "store-init-" + id);
3063
+ }
3064
+ }
3065
+ }
3066
+ });
3067
+ }
3068
+ function collectDeviceInfo() {
3069
+ const nav = typeof navigator !== "undefined" ? navigator : void 0;
3070
+ const scr = typeof window !== "undefined" && window.screen ? window.screen : void 0;
3071
+ const win = typeof window !== "undefined" ? window : void 0;
3072
+ const runtimePlatform = detectRuntimePlatform();
3073
+ const perf = performance;
3074
+ const mem = typeof perf.memory === "object" && perf.memory !== null ? perf.memory : void 0;
3075
+ const libVersion = runtimePlatform === "web" ? detectBrowserVersion(nav?.userAgent) : detectMpLibVersion(globalThis.wx);
3076
+ function safeAreaInset(side) {
3077
+ if (typeof getComputedStyle !== "function" || typeof document === "undefined") return void 0;
3078
+ const raw = getComputedStyle(document.documentElement).getPropertyValue("env(safe-area-inset-" + side + ")").trim();
3079
+ const px = /^[\d.]+px$/.test(raw) ? parseFloat(raw) : NaN;
3080
+ return Number.isFinite(px) ? px : void 0;
3081
+ }
3082
+ return {
3083
+ platform: runtimePlatform,
3084
+ userAgent: nav?.userAgent,
3085
+ libVersion,
3086
+ screen: {
3087
+ dpr: win?.devicePixelRatio || 1,
3088
+ width: scr?.width ?? 0,
3089
+ height: scr?.height ?? 0,
3090
+ safeTop: safeAreaInset("top"),
3091
+ safeBottom: safeAreaInset("bottom")
3092
+ },
3093
+ memory: mem ? { jsHeapLimit: mem.jsHeapLimit, totalJSHeapSize: mem.totalJSHeapSize, usedJSHeapSize: mem.usedJSHeapSize } : void 0,
3094
+ // ★能力表按真实运行平台探测(web 有 window → 解析 web adapter)
3095
+ capabilities: globalRegistry.snapshot(runtimePlatform)
3096
+ };
3097
+ }
3098
+ function installProteusDevtools(app, options = {}) {
3099
+ const bus = options.traceBus ?? getProteusTraceBus();
3100
+ setCapabilityTraceBus(bus);
3101
+ let storeTracer = null;
3102
+ if (options.pinia) {
3103
+ storeTracer = createStoreTracer(options.pinia, bus);
3104
+ }
3105
+ const componentTrace = installComponentTrace(app, bus);
3106
+ const ownershipDisabled = options.ownership === false;
3107
+ const ownershipOpts = typeof options.ownership === "object" ? options.ownership : void 0;
3108
+ const ownershipTracer = ownershipDisabled ? null : createOwnershipTracer({ graph: ownershipOpts?.graph, history: ownershipOpts?.history });
3109
+ const collectOwnershipData = ownershipTracer ? () => ownershipTracer.collect() : void 0;
3110
+ function highlightComponent(id) {
3111
+ const el = componentTrace.getElement(id);
3112
+ if (!el) return;
3113
+ el.scrollIntoView({ behavior: "smooth", block: "center" });
3114
+ el.classList.add("pd-cmp-highlight");
3115
+ setTimeout(() => el.classList.remove("pd-cmp-highlight"), 1500);
3116
+ const dom = buildDomTree(el);
3117
+ bus.emit("component", "point", "component.inspect", { id, dom }, "comp-" + id);
3118
+ }
3119
+ const navRecords = [];
3120
+ let navCurrent = "";
3121
+ let navInflight = null;
3122
+ const offNav = bus.on((e) => {
3123
+ if (e.source !== "router") return;
3124
+ if (e.phase === "start" && /nav/i.test(e.name)) {
3125
+ const p = e.payload ?? {};
3126
+ navInflight = { from: p.from?.path ?? "?", to: p.to?.path ?? e.name, query: p.to?.query, start: e.timestamp, traceId: e.traceId, guards: [] };
3127
+ } else if (navInflight && e.phase === "point" && /guard/i.test(e.name)) {
3128
+ let result = "next";
3129
+ if (/redirect/i.test(e.name)) result = "redirect";
3130
+ else if (/cancel/i.test(e.name)) result = "cancel";
3131
+ else if (/error/i.test(e.name)) result = "error";
3132
+ navInflight.guards.push({ name: e.name, result });
3133
+ } else if (e.phase === "end" && /nav/i.test(e.name) && navInflight) {
3134
+ const rec = {
3135
+ from: navInflight.from,
3136
+ to: navInflight.to,
3137
+ query: navInflight.query,
3138
+ durationMs: Math.max(0, e.timestamp - navInflight.start),
3139
+ timestamp: e.timestamp,
3140
+ traceId: e.traceId ?? navInflight.traceId,
3141
+ guards: navInflight.guards
3142
+ };
3143
+ navRecords.push(rec);
3144
+ navCurrent = rec.to;
3145
+ if (navRecords.length > 50) navRecords.shift();
3146
+ navInflight = null;
3147
+ }
3148
+ });
3149
+ setupDevtoolsPlugin({ ...PROTEUS_DEVTOOLS_PLUGIN_DESCRIPTOR, app }, (devtoolsApi) => {
3150
+ installProteusTimeline(devtoolsApi, { source: createTraceBusSource(bus) });
3151
+ installProteusInspectors(devtoolsApi, {
3152
+ getConfig: options.getConfig,
3153
+ setConfig: options.setConfig,
3154
+ getStyleSafetyRecords: options.styleGuard ? () => options.styleGuard?.records() ?? [] : void 0,
3155
+ pages: options.pages,
3156
+ getRouterState: () => ({ currentRoute: navCurrent || void 0, records: [...navRecords] })
3157
+ });
3158
+ });
3159
+ if (options.mount !== false) {
3160
+ mountFloatingPanel(
3161
+ bus,
3162
+ options.pages,
3163
+ (stores) => {
3164
+ if (!options.pinia) return;
3165
+ for (const s of stores) {
3166
+ const store = options.pinia._s.get(s.id);
3167
+ store?.$patch?.(s.state);
3168
+ }
3169
+ },
3170
+ highlightComponent,
3171
+ options.pinia,
3172
+ collectDeviceInfo,
3173
+ collectOwnershipData
3174
+ );
3175
+ }
3176
+ let remoteBridge = null;
3177
+ if (options.remote) {
3178
+ const remoteOpts = typeof options.remote === "object" ? options.remote : null;
3179
+ const path = remoteOpts !== null && remoteOpts.path ? remoteOpts.path : "/proteus-source";
3180
+ const protocol = typeof location !== "undefined" && location.protocol === "https:" ? "wss" : "ws";
3181
+ const url = `${protocol}://${typeof location !== "undefined" ? location.host : "localhost"}${path}`;
3182
+ remoteBridge = createTraceBusWsBridge(bus, {
3183
+ url,
3184
+ appInfo: remoteOpts !== null && remoteOpts.appInfo ? remoteOpts.appInfo : () => options.pages,
3185
+ // ★M8 设备面板:应用侧环境/能力上报(与本地面板同源采集闭包)
3186
+ deviceInfo: collectDeviceInfo,
3187
+ // ★G-43 B4 所有权面板:应用侧视图数据上报(与本地面板同源 tracer)
3188
+ ownership: collectOwnershipData,
3189
+ // ★远程时间旅行:面板 Proteus.restoreStores 命令 → 应用侧逐 store $patch 恢复(复用 applyState 语义)
3190
+ onRestoreStores: (stores) => {
3191
+ if (!options.pinia) return;
3192
+ for (const s of stores) {
3193
+ const store = options.pinia._s.get(s.id);
3194
+ store?.$patch?.(s.state);
3195
+ }
3196
+ }
3197
+ });
3198
+ }
3199
+ let offHmr = null;
3200
+ if (options.hmr) {
3201
+ const offs = [];
3202
+ offs.push(options.hmr.on("vite:beforeUpdate", () => bus.emit("hmr", "point", "vite:update")) ?? null);
3203
+ offs.push(options.hmr.on("vite:beforeFullReload", () => bus.emit("hmr", "point", "vite:full-reload")) ?? null);
3204
+ offs.push(
3205
+ options.hmr.on(
3206
+ "vite:error",
3207
+ (err) => bus.emit("hmr", "error", "vite:error", { message: err instanceof Error ? err.message : String(err) })
3208
+ ) ?? null
3209
+ );
3210
+ offHmr = () => {
3211
+ for (const f of offs) f?.();
3212
+ };
3213
+ }
3214
+ return {
3215
+ traceBus: bus,
3216
+ destroy() {
3217
+ storeTracer?.dispose();
3218
+ componentTrace.dispose();
3219
+ ownershipTracer?.dispose();
3220
+ offNav();
3221
+ remoteBridge?.close();
3222
+ offHmr?.();
3223
+ }
3224
+ };
3225
+ }
3226
+
3227
+ // src/plugins/network.ts
3228
+ function createNetworkPlugin() {
3229
+ return {
3230
+ name: "@proteus-vue/devtools-plugin-network",
3231
+ version: "0.1.0",
3232
+ setup(ctx) {
3233
+ const rows = [];
3234
+ const inflight = /* @__PURE__ */ new Map();
3235
+ ctx.bus.on((e) => {
3236
+ if (e.source !== "api") return;
3237
+ if (e.phase === "start") {
3238
+ inflight.set(e.traceId ?? e.name + "-" + e.timestamp, e.timestamp);
3239
+ } else if (e.phase === "end" || e.phase === "error") {
3240
+ const start = inflight.get(e.traceId ?? e.name + "-" + e.timestamp);
3241
+ rows.unshift({
3242
+ name: e.name,
3243
+ ms: start !== void 0 ? Math.max(0, e.timestamp - start) : 0,
3244
+ ok: e.phase === "end",
3245
+ ts: e.timestamp
3246
+ });
3247
+ if (rows.length > 200) rows.pop();
3248
+ }
3249
+ });
3250
+ ctx.panel.addView("network", {
3251
+ label: "network",
3252
+ icon: "\u21C5",
3253
+ render(container) {
3254
+ container.replaceChildren();
3255
+ if (rows.length === 0) {
3256
+ const empty = document.createElement("div");
3257
+ empty.className = "pd-empty";
3258
+ empty.textContent = "\u6682\u65E0 API \u4E8B\u4EF6\uFF08source=api\uFF09";
3259
+ container.appendChild(empty);
3260
+ return;
3261
+ }
3262
+ const list = document.createElement("div");
3263
+ list.className = "pd-net";
3264
+ for (const r of rows.slice(0, 50)) {
3265
+ const row = document.createElement("div");
3266
+ row.className = "pd-net-row" + (r.ok ? "" : " pd-net-error");
3267
+ const name = document.createElement("span");
3268
+ name.className = "pd-net-name";
3269
+ name.textContent = r.name;
3270
+ const meta = document.createElement("span");
3271
+ meta.className = "pd-net-meta";
3272
+ meta.textContent = r.ms + "ms" + (r.ok ? "" : " \u2715");
3273
+ row.appendChild(name);
3274
+ row.appendChild(meta);
3275
+ list.appendChild(row);
3276
+ }
3277
+ container.appendChild(list);
3278
+ }
3279
+ });
3280
+ }
3281
+ };
3282
+ }
3283
+ export {
3284
+ PROTEUS_DEVTOOLS_PLUGIN_DESCRIPTOR,
3285
+ attachTip,
3286
+ bindTooltip,
3287
+ buildDomTree,
3288
+ createCommandRegistry,
3289
+ createDevtoolsPanel,
3290
+ createDevtoolsWsSource,
3291
+ createMemoryStorage,
3292
+ createNetworkPlugin,
3293
+ createOwnershipTracer,
3294
+ createPluginRegistry,
3295
+ createTimelineZoom,
3296
+ createTooltipLayer,
3297
+ createTraceBusSource,
3298
+ createTraceBusWsBridge,
3299
+ detectBrowserVersion,
3300
+ detectMpLibVersion,
3301
+ detectRuntimePlatform,
3302
+ findSensitiveKeys,
3303
+ installComponentTrace,
3304
+ installProteusDevtools,
3305
+ installProteusInspectors,
3306
+ installProteusTimeline,
3307
+ parseSession,
3308
+ parseStoreSnapshot,
3309
+ renderComponents,
3310
+ renderDevice,
3311
+ renderErrors,
3312
+ renderFlamegraph,
3313
+ renderGraph,
3314
+ renderOwnership,
3315
+ renderPages,
3316
+ renderRoute,
3317
+ renderState,
3318
+ renderTimeline,
3319
+ resolveActivationOrder,
3320
+ resolveTipData,
3321
+ serializeSession,
3322
+ serializeStoreSnapshot
3323
+ };