nuxt-devtools-observatory 0.1.25 → 0.1.28
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/README.md +35 -22
- package/client/dist/assets/index-DXCGQOSF.js +17 -0
- package/client/dist/assets/index-htI4WwBU.css +1 -0
- package/client/dist/index.html +2 -2
- package/client/src/App.vue +2 -6
- package/client/src/stores/observatory.ts +152 -218
- package/client/src/views/ComposableTracker.vue +5 -37
- package/client/src/views/FetchDashboard.vue +2 -1
- package/client/src/views/ProvideInjectGraph.vue +4 -11
- package/client/src/views/RenderHeatmap.vue +3 -12
- package/client/src/views/TransitionTimeline.vue +2 -1
- package/client/vite.config.ts +4 -1
- package/dist/module.d.mts +6 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +82 -30
- package/dist/runtime/composables/fetch-registry.js +3 -0
- package/dist/runtime/plugin.js +93 -66
- package/package.json +8 -2
- package/client/dist/assets/index-1-H6UMCK.css +0 -1
- package/client/dist/assets/index-eSUuhYQ0.js +0 -17
package/dist/module.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { defineNuxtModule, createResolver, addVitePlugin, addPlugin, addServerPlugin } from '@nuxt/kit';
|
|
2
|
+
import { onDevToolsInitialized, extendServerRpc } from '@nuxt/devtools-kit';
|
|
3
|
+
import sirv from 'sirv';
|
|
2
4
|
import { parse } from '@babel/parser';
|
|
3
5
|
import _traverse from '@babel/traverse';
|
|
4
6
|
import _generate from '@babel/generator';
|
|
@@ -544,6 +546,8 @@ function transitionTrackerPlugin() {
|
|
|
544
546
|
}
|
|
545
547
|
|
|
546
548
|
const defaults = {
|
|
549
|
+
// Auto-enable server instrumentation for SSR projects unless explicitly overridden.
|
|
550
|
+
// This ensures initial SSR snapshots include server-side composables/fetch events.
|
|
547
551
|
instrumentServer: process.env.OBSERVATORY_INSTRUMENT_SERVER === "true",
|
|
548
552
|
fetchDashboard: process.env.OBSERVATORY_FETCH_DASHBOARD === "true",
|
|
549
553
|
provideInjectGraph: process.env.OBSERVATORY_PROVIDE_INJECT_GRAPH === "true",
|
|
@@ -559,7 +563,8 @@ const defaults = {
|
|
|
559
563
|
maxComposableEntries: process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES) : 300,
|
|
560
564
|
maxRenderTimeline: process.env.OBSERVATORY_MAX_RENDER_TIMELINE ? Number(process.env.OBSERVATORY_MAX_RENDER_TIMELINE) : 100,
|
|
561
565
|
composableNavigationMode: process.env.OBSERVATORY_COMPOSABLE_NAVIGATION_MODE === "session" ? "session" : "route",
|
|
562
|
-
heatmapHideInternals: process.env.OBSERVATORY_HEATMAP_HIDE_INTERNALS === "true"
|
|
566
|
+
heatmapHideInternals: process.env.OBSERVATORY_HEATMAP_HIDE_INTERNALS === "true",
|
|
567
|
+
debugRpc: process.env.OBSERVATORY_DEBUG_RPC === "true"
|
|
563
568
|
};
|
|
564
569
|
const module$1 = defineNuxtModule({
|
|
565
570
|
meta: {
|
|
@@ -586,7 +591,7 @@ const module$1 = defineNuxtModule({
|
|
|
586
591
|
composableTracker: options.composableTracker ?? (process.env.OBSERVATORY_COMPOSABLE_TRACKER ? process.env.OBSERVATORY_COMPOSABLE_TRACKER === "true" : true),
|
|
587
592
|
renderHeatmap: options.renderHeatmap ?? (process.env.OBSERVATORY_RENDER_HEATMAP ? process.env.OBSERVATORY_RENDER_HEATMAP === "true" : true),
|
|
588
593
|
transitionTracker: options.transitionTracker ?? (process.env.OBSERVATORY_TRANSITION_TRACKER ? process.env.OBSERVATORY_TRANSITION_TRACKER === "true" : true),
|
|
589
|
-
instrumentServer: options.instrumentServer ?? (process.env.OBSERVATORY_INSTRUMENT_SERVER ? process.env.OBSERVATORY_INSTRUMENT_SERVER === "true" : false),
|
|
594
|
+
instrumentServer: options.instrumentServer ?? (process.env.OBSERVATORY_INSTRUMENT_SERVER ? process.env.OBSERVATORY_INSTRUMENT_SERVER === "true" : nuxt.options.ssr !== false),
|
|
590
595
|
heatmapThresholdCount: options.heatmapThresholdCount ?? (process.env.OBSERVATORY_HEATMAP_THRESHOLD_COUNT ? Number(process.env.OBSERVATORY_HEATMAP_THRESHOLD_COUNT) : 3),
|
|
591
596
|
heatmapThresholdTime: options.heatmapThresholdTime ?? (process.env.OBSERVATORY_HEATMAP_THRESHOLD_TIME ? Number(process.env.OBSERVATORY_HEATMAP_THRESHOLD_TIME) : 1600),
|
|
592
597
|
maxFetchEntries: options.maxFetchEntries ?? (process.env.OBSERVATORY_MAX_FETCH_ENTRIES ? Number(process.env.OBSERVATORY_MAX_FETCH_ENTRIES) : 200),
|
|
@@ -595,7 +600,8 @@ const module$1 = defineNuxtModule({
|
|
|
595
600
|
maxComposableHistory: options.maxComposableHistory ?? (process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY) : 50),
|
|
596
601
|
maxComposableEntries: options.maxComposableEntries ?? (process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES) : 300),
|
|
597
602
|
maxRenderTimeline: options.maxRenderTimeline ?? (process.env.OBSERVATORY_MAX_RENDER_TIMELINE ? Number(process.env.OBSERVATORY_MAX_RENDER_TIMELINE) : 100),
|
|
598
|
-
composableNavigationMode: options.composableNavigationMode ?? (process.env.OBSERVATORY_COMPOSABLE_NAVIGATION_MODE === "session" ? "session" : "route")
|
|
603
|
+
composableNavigationMode: options.composableNavigationMode ?? (process.env.OBSERVATORY_COMPOSABLE_NAVIGATION_MODE === "session" ? "session" : "route"),
|
|
604
|
+
debugRpc: options.debugRpc ?? (process.env.OBSERVATORY_DEBUG_RPC ? process.env.OBSERVATORY_DEBUG_RPC === "true" : false)
|
|
599
605
|
};
|
|
600
606
|
nuxt.hook("vite:extendConfig", (config) => {
|
|
601
607
|
const alias = config.resolve?.alias;
|
|
@@ -626,34 +632,80 @@ const module$1 = defineNuxtModule({
|
|
|
626
632
|
if (resolved.fetchDashboard) {
|
|
627
633
|
addServerPlugin(resolver.resolve("./runtime/nitro/fetch-capture"));
|
|
628
634
|
}
|
|
629
|
-
const
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
+
const base = "/__observatory";
|
|
636
|
+
const debugRpc = resolved.debugRpc === true;
|
|
637
|
+
const debugLog = (...args) => {
|
|
638
|
+
if (debugRpc) {
|
|
639
|
+
console.info("[observatory][rpc][server]", ...args);
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
let latestSnapshot = {
|
|
643
|
+
fetch: [],
|
|
644
|
+
provideInject: { provides: [], injects: [] },
|
|
645
|
+
composables: [],
|
|
646
|
+
renders: [],
|
|
647
|
+
transitions: [],
|
|
648
|
+
features: {
|
|
649
|
+
fetchDashboard: !!resolved.fetchDashboard,
|
|
650
|
+
provideInjectGraph: !!resolved.provideInjectGraph,
|
|
651
|
+
composableTracker: !!resolved.composableTracker,
|
|
652
|
+
composableNavigationMode: resolved.composableNavigationMode,
|
|
653
|
+
renderHeatmap: !!resolved.renderHeatmap,
|
|
654
|
+
transitionTracker: !!resolved.transitionTracker
|
|
635
655
|
}
|
|
636
|
-
|
|
656
|
+
};
|
|
657
|
+
let rpc = null;
|
|
658
|
+
let viteServer = null;
|
|
659
|
+
const emitCommand = (command) => {
|
|
660
|
+
if (!viteServer) {
|
|
661
|
+
console.warn("[observatory][rpc][server] command dropped (vite ws not ready)", command);
|
|
637
662
|
return;
|
|
638
663
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
664
|
+
debugLog("send command", command);
|
|
665
|
+
viteServer.ws.send("observatory:command", command);
|
|
666
|
+
};
|
|
667
|
+
addVitePlugin({
|
|
668
|
+
name: "nuxt-devtools-observatory:sirv-client",
|
|
669
|
+
configureServer(server) {
|
|
670
|
+
viteServer = server;
|
|
671
|
+
const clientDist = resolver.resolve("../client/dist");
|
|
672
|
+
server.middlewares.use(base, sirv(clientDist, { dev: true, single: true }));
|
|
673
|
+
server.ws.on("observatory:snapshot", (snapshot) => {
|
|
674
|
+
latestSnapshot = snapshot;
|
|
675
|
+
debugLog("received host snapshot", {
|
|
676
|
+
fetch: Array.isArray(snapshot.fetch) ? snapshot.fetch.length : 0,
|
|
677
|
+
composables: Array.isArray(snapshot.composables) ? snapshot.composables.length : 0,
|
|
678
|
+
renders: Array.isArray(snapshot.renders) ? snapshot.renders.length : 0,
|
|
679
|
+
transitions: Array.isArray(snapshot.transitions) ? snapshot.transitions.length : 0
|
|
680
|
+
});
|
|
681
|
+
rpc?.broadcast.onSnapshot.asEvent(snapshot);
|
|
682
|
+
});
|
|
683
|
+
}
|
|
655
684
|
});
|
|
656
|
-
|
|
685
|
+
onDevToolsInitialized(() => {
|
|
686
|
+
rpc = extendServerRpc(
|
|
687
|
+
"observatory",
|
|
688
|
+
{
|
|
689
|
+
async getSnapshot() {
|
|
690
|
+
return latestSnapshot;
|
|
691
|
+
},
|
|
692
|
+
async requestSnapshot() {
|
|
693
|
+
emitCommand({ cmd: "request-snapshot" });
|
|
694
|
+
},
|
|
695
|
+
async clearComposables() {
|
|
696
|
+
emitCommand({ cmd: "clear-composables" });
|
|
697
|
+
},
|
|
698
|
+
async setComposableMode(mode) {
|
|
699
|
+
emitCommand({ cmd: "set-mode", mode });
|
|
700
|
+
},
|
|
701
|
+
async editComposableValue(id, key, value) {
|
|
702
|
+
emitCommand({ cmd: "edit-composable", id, key, value });
|
|
703
|
+
}
|
|
704
|
+
},
|
|
705
|
+
nuxt
|
|
706
|
+
);
|
|
707
|
+
rpc.broadcast.onSnapshot.asEvent(latestSnapshot);
|
|
708
|
+
}, nuxt);
|
|
657
709
|
nuxt.hook("render:response", (response, { url }) => {
|
|
658
710
|
if (url.startsWith("/trackers") || url === "/" || url.startsWith("/index.html")) {
|
|
659
711
|
const configScript = `<script>window.__observatoryConfig = ${JSON.stringify(nuxt.options.runtimeConfig.public.observatory)};<\/script>`;
|
|
@@ -667,13 +719,12 @@ ${configScript}`);
|
|
|
667
719
|
name: "observatory-trackers",
|
|
668
720
|
title: "Observatory Trackers",
|
|
669
721
|
icon: "carbon:heat-map",
|
|
670
|
-
view: { type: "iframe", src: `${base}/trackers` }
|
|
722
|
+
view: { type: "iframe", src: `${base}/trackers${resolved.debugRpc ? "?debugRpc=1" : ""}` }
|
|
671
723
|
});
|
|
672
724
|
}
|
|
673
725
|
});
|
|
674
726
|
nuxt.options.runtimeConfig.public.observatory = {
|
|
675
727
|
instrumentServer: resolved.instrumentServer,
|
|
676
|
-
clientOrigin,
|
|
677
728
|
fetchDashboard: resolved.fetchDashboard,
|
|
678
729
|
provideInjectGraph: resolved.provideInjectGraph,
|
|
679
730
|
composableTracker: resolved.composableTracker,
|
|
@@ -688,7 +739,8 @@ ${configScript}`);
|
|
|
688
739
|
composableNavigationMode: resolved.composableNavigationMode,
|
|
689
740
|
heatmapHideInternals: resolved.heatmapHideInternals,
|
|
690
741
|
heatmapThresholdCount: resolved.heatmapThresholdCount,
|
|
691
|
-
heatmapThresholdTime: resolved.heatmapThresholdTime
|
|
742
|
+
heatmapThresholdTime: resolved.heatmapThresholdTime,
|
|
743
|
+
debugRpc: resolved.debugRpc
|
|
692
744
|
};
|
|
693
745
|
}
|
|
694
746
|
});
|
|
@@ -218,6 +218,9 @@ export function __devFetchCall(originalFn, url, opts, meta) {
|
|
|
218
218
|
}
|
|
219
219
|
},
|
|
220
220
|
onResponseError({ response }) {
|
|
221
|
+
const endTime = performance.now();
|
|
222
|
+
const ms = Math.round(endTime - lastCallStart);
|
|
223
|
+
registry.update(id, { status: "error", endTime, ms });
|
|
221
224
|
if (typeof opts.onResponseError === "function") {
|
|
222
225
|
opts.onResponseError({ response });
|
|
223
226
|
}
|
package/dist/runtime/plugin.js
CHANGED
|
@@ -11,7 +11,15 @@ export default defineNuxtPlugin(() => {
|
|
|
11
11
|
}
|
|
12
12
|
const nuxtApp = useNuxtApp();
|
|
13
13
|
const config = useRuntimeConfig().public.observatory;
|
|
14
|
+
const debugRpc = config.debugRpc === true;
|
|
15
|
+
const debugLog = (...args) => {
|
|
16
|
+
if (debugRpc) {
|
|
17
|
+
console.info("[observatory][rpc][host]", ...args);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
14
20
|
let composableNavigationMode = config.composableNavigationMode === "session" ? "session" : "route";
|
|
21
|
+
let heartbeatId = null;
|
|
22
|
+
let lastSnapshotSignature = "";
|
|
15
23
|
if (config.renderHeatmap) {
|
|
16
24
|
nuxtApp.vueApp.config.performance = true;
|
|
17
25
|
}
|
|
@@ -36,75 +44,80 @@ export default defineNuxtPlugin(() => {
|
|
|
36
44
|
if (import.meta.client) {
|
|
37
45
|
delete window.__observatory__;
|
|
38
46
|
window.__observatory__ = registries;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
const composableRegistry = registries.composable;
|
|
48
|
+
if (composableRegistry && composableRegistry.onComposableChange) {
|
|
49
|
+
composableRegistry.onComposableChange(() => {
|
|
50
|
+
broadcastAll("composable:onChange");
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
import.meta.hot?.on("observatory:command", (rawPayload) => {
|
|
54
|
+
if (!rawPayload || typeof rawPayload !== "object") {
|
|
43
55
|
return;
|
|
44
56
|
}
|
|
45
|
-
const
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const source = event.source;
|
|
50
|
-
source?.postMessage({ type: "observatory:snapshot", data: buildSnapshot() }, event.origin);
|
|
57
|
+
const payload = rawPayload;
|
|
58
|
+
if (payload.cmd === "request-snapshot") {
|
|
59
|
+
debugLog("received command: request-snapshot");
|
|
60
|
+
broadcastAll("command:request-snapshot");
|
|
51
61
|
return;
|
|
52
62
|
}
|
|
53
|
-
if (
|
|
63
|
+
if (payload.cmd === "clear-composables") {
|
|
64
|
+
debugLog("received command: clear-composables");
|
|
54
65
|
if (composableRegistry) {
|
|
55
|
-
|
|
66
|
+
if (composableNavigationMode === "session") {
|
|
67
|
+
composableRegistry.clearNonLayout();
|
|
68
|
+
} else {
|
|
69
|
+
composableRegistry.clear();
|
|
70
|
+
}
|
|
56
71
|
}
|
|
57
|
-
|
|
58
|
-
source?.postMessage({ type: "observatory:snapshot", data: buildSnapshot() }, event.origin);
|
|
72
|
+
broadcastAll("command:clear-composables");
|
|
59
73
|
return;
|
|
60
74
|
}
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
if (mode === "route" || mode === "session") {
|
|
64
|
-
composableNavigationMode = mode;
|
|
65
|
-
}
|
|
66
|
-
const source = event.source;
|
|
67
|
-
source?.postMessage({ type: "observatory:snapshot", data: buildSnapshot() }, event.origin);
|
|
68
|
-
}
|
|
69
|
-
if (type === "observatory:edit-composable") {
|
|
70
|
-
const { id, key, value } = event.data;
|
|
71
|
-
if (composableRegistry) {
|
|
72
|
-
composableRegistry.editValue(id, key, value);
|
|
75
|
+
if (payload.cmd === "set-mode") {
|
|
76
|
+
debugLog("received command: set-mode", payload.mode);
|
|
77
|
+
if (payload.mode === "route" || payload.mode === "session") {
|
|
78
|
+
composableNavigationMode = payload.mode;
|
|
73
79
|
}
|
|
80
|
+
broadcastAll("command:set-mode");
|
|
81
|
+
return;
|
|
74
82
|
}
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const cleaned = file.replace(/^\/@fs/, "").replace(/\?.*$/, "");
|
|
79
|
-
fetch(`/__open-in-editor?file=${encodeURIComponent(cleaned)}`).catch(() => {
|
|
80
|
-
});
|
|
81
|
-
}
|
|
83
|
+
if (payload.cmd === "edit-composable") {
|
|
84
|
+
debugLog("received command: edit-composable", { id: payload.id, key: payload.key });
|
|
85
|
+
composableRegistry?.editValue(payload.id, payload.key, payload.value);
|
|
82
86
|
}
|
|
83
|
-
};
|
|
84
|
-
window.addEventListener("message", messageHandler);
|
|
87
|
+
});
|
|
85
88
|
nuxtApp.hook("app:beforeUnmount", () => {
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
import.meta.hot?.off("observatory:command");
|
|
90
|
+
if (heartbeatId !== null) {
|
|
91
|
+
window.clearInterval(heartbeatId);
|
|
92
|
+
heartbeatId = null;
|
|
93
|
+
}
|
|
88
94
|
});
|
|
89
|
-
const composableRegistry = registries.composable;
|
|
90
|
-
if (composableRegistry && composableRegistry.onComposableChange) {
|
|
91
|
-
composableRegistry.onComposableChange(() => {
|
|
92
|
-
const lastMessageSource = lastMessageSourceRef?.deref();
|
|
93
|
-
if (!lastMessageSource || !lastMessageOrigin) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
lastMessageSource.postMessage(
|
|
97
|
-
{
|
|
98
|
-
type: "observatory:snapshot",
|
|
99
|
-
data: buildSnapshot()
|
|
100
|
-
},
|
|
101
|
-
lastMessageOrigin
|
|
102
|
-
);
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
95
|
}
|
|
106
96
|
nuxtApp.hook("app:mounted", () => {
|
|
107
|
-
broadcastAll();
|
|
97
|
+
broadcastAll("app:mounted");
|
|
98
|
+
nextTick(() => {
|
|
99
|
+
broadcastAll("app:mounted:nextTick");
|
|
100
|
+
});
|
|
101
|
+
setTimeout(() => {
|
|
102
|
+
broadcastAll("app:mounted:50ms");
|
|
103
|
+
}, 50);
|
|
104
|
+
setTimeout(() => {
|
|
105
|
+
broadcastAll("app:mounted:250ms");
|
|
106
|
+
}, 250);
|
|
107
|
+
if (import.meta.client && heartbeatId === null) {
|
|
108
|
+
heartbeatId = window.setInterval(() => {
|
|
109
|
+
const snapshot = buildSnapshot();
|
|
110
|
+
const signature = JSON.stringify(snapshot);
|
|
111
|
+
if (signature !== lastSnapshotSignature) {
|
|
112
|
+
lastSnapshotSignature = signature;
|
|
113
|
+
debugLog("heartbeat detected snapshot change");
|
|
114
|
+
import.meta.hot?.send("observatory:snapshot", snapshot);
|
|
115
|
+
}
|
|
116
|
+
}, 400);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
nuxtApp.hook("page:finish", () => {
|
|
120
|
+
broadcastAll("page:finish");
|
|
108
121
|
});
|
|
109
122
|
if (import.meta.client) {
|
|
110
123
|
const router = useRouter();
|
|
@@ -114,16 +127,25 @@ export default defineNuxtPlugin(() => {
|
|
|
114
127
|
return;
|
|
115
128
|
}
|
|
116
129
|
const render = registries.render;
|
|
117
|
-
if (render && typeof render.reset === "function")
|
|
130
|
+
if (render && typeof render.reset === "function") {
|
|
131
|
+
;
|
|
132
|
+
render.reset();
|
|
133
|
+
}
|
|
118
134
|
const provideInject = registries.provideInject;
|
|
119
|
-
if (provideInject && typeof provideInject.clear === "function")
|
|
135
|
+
if (provideInject && typeof provideInject.clear === "function") {
|
|
136
|
+
;
|
|
120
137
|
provideInject.clear();
|
|
138
|
+
}
|
|
121
139
|
const composable = registries.composable;
|
|
122
|
-
if (composableNavigationMode === "route" && composable && typeof composable.clearNonLayout === "function")
|
|
140
|
+
if (composableNavigationMode === "route" && composable && typeof composable.clearNonLayout === "function") {
|
|
141
|
+
;
|
|
123
142
|
composable.clearNonLayout();
|
|
143
|
+
}
|
|
124
144
|
const transition = registries.transition;
|
|
125
|
-
if (transition && typeof transition.clear === "function")
|
|
145
|
+
if (transition && typeof transition.clear === "function") {
|
|
146
|
+
;
|
|
126
147
|
transition.clear();
|
|
148
|
+
}
|
|
127
149
|
}
|
|
128
150
|
);
|
|
129
151
|
router.afterEach((to) => {
|
|
@@ -137,18 +159,26 @@ export default defineNuxtPlugin(() => {
|
|
|
137
159
|
;
|
|
138
160
|
render.setRoute(to.path ?? "/");
|
|
139
161
|
}
|
|
140
|
-
nextTick(() => broadcastAll());
|
|
162
|
+
nextTick(() => broadcastAll("router:afterEach"));
|
|
141
163
|
});
|
|
142
164
|
}
|
|
143
|
-
function broadcastAll() {
|
|
165
|
+
function broadcastAll(reason = "unknown") {
|
|
144
166
|
if (!import.meta.client) {
|
|
145
167
|
return;
|
|
146
168
|
}
|
|
147
|
-
|
|
148
|
-
if (!channel) {
|
|
169
|
+
if (!import.meta.hot) {
|
|
149
170
|
return;
|
|
150
171
|
}
|
|
151
|
-
|
|
172
|
+
const snapshot = buildSnapshot();
|
|
173
|
+
debugLog("push snapshot", {
|
|
174
|
+
reason,
|
|
175
|
+
fetch: Array.isArray(snapshot.fetch) ? snapshot.fetch.length : 0,
|
|
176
|
+
composables: Array.isArray(snapshot.composables) ? snapshot.composables.length : 0,
|
|
177
|
+
renders: Array.isArray(snapshot.renders) ? snapshot.renders.length : 0,
|
|
178
|
+
transitions: Array.isArray(snapshot.transitions) ? snapshot.transitions.length : 0
|
|
179
|
+
});
|
|
180
|
+
lastSnapshotSignature = JSON.stringify(snapshot);
|
|
181
|
+
import.meta.hot.send("observatory:snapshot", snapshot);
|
|
152
182
|
}
|
|
153
183
|
function buildSnapshot() {
|
|
154
184
|
function safeParse(val, fallback) {
|
|
@@ -187,7 +217,4 @@ export default defineNuxtPlugin(() => {
|
|
|
187
217
|
};
|
|
188
218
|
return snapshot;
|
|
189
219
|
}
|
|
190
|
-
function getDevtoolsChannel() {
|
|
191
|
-
return window.__nuxt_devtools__?.channel ?? null;
|
|
192
|
-
}
|
|
193
220
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-devtools-observatory",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.28",
|
|
4
4
|
"description": "Nuxt DevTools: useFetch Dashboard, provide/inject Graph, Composable Tracker, Render Heatmap",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -35,11 +35,15 @@
|
|
|
35
35
|
],
|
|
36
36
|
"scripts": {
|
|
37
37
|
"dev": "nuxi dev playground",
|
|
38
|
+
"docs:dev": "pnpm --dir docs dev",
|
|
39
|
+
"docs:build": "pnpm --dir docs build",
|
|
40
|
+
"docs:generate": "pnpm --dir docs generate",
|
|
41
|
+
"docs:preview": "pnpm --dir docs preview",
|
|
38
42
|
"build:client": "vite build --config client/vite.config.ts",
|
|
39
43
|
"build": "npm run build:client && nuxt-module-build build",
|
|
40
44
|
"prepack": "npm run build",
|
|
41
45
|
"lint": "eslint .",
|
|
42
|
-
"format": "prettier --write '**/*.{ts,vue,js,json}' --ignore-path .prettierignore && stylelint '**/*.{css,vue}' --ignore-pattern '
|
|
46
|
+
"format": "prettier --write '**/*.{ts,vue,js,json}' --ignore-path .prettierignore && stylelint '**/*.{css,vue}' --ignore-pattern '**/.nuxt/**' --ignore-pattern '**/.output/**' --ignore-pattern 'coverage/**' --ignore-pattern 'client/dist/**' --ignore-pattern 'scripts/**' --fix && eslint --fix '**/*.{ts,vue,js}' --ignore-pattern '**/.nuxt/**' --ignore-pattern '**/.output/**' --ignore-pattern 'coverage/**' --ignore-pattern 'client/dist/**' --ignore-pattern 'scripts/**'",
|
|
43
47
|
"typecheck": "vue-tsc --noEmit",
|
|
44
48
|
"test": "vitest run",
|
|
45
49
|
"test:watch": "vitest",
|
|
@@ -56,6 +60,7 @@
|
|
|
56
60
|
},
|
|
57
61
|
"devDependencies": {
|
|
58
62
|
"@eslint/js": "^9.39.2",
|
|
63
|
+
"@nuxt/devtools-kit": "4.0.0-alpha.3",
|
|
59
64
|
"@nuxt/eslint": "^1.13.0",
|
|
60
65
|
"@nuxt/kit": "^3.0.0",
|
|
61
66
|
"@nuxt/module-builder": "^1.0.2",
|
|
@@ -80,6 +85,7 @@
|
|
|
80
85
|
"playwright": "^1.58.2",
|
|
81
86
|
"postcss-html": "^1.8.1",
|
|
82
87
|
"prettier": "^3.8.1",
|
|
88
|
+
"sirv": "^3.0.2",
|
|
83
89
|
"stylelint": "^17.4.0",
|
|
84
90
|
"stylelint-config-standard": "^40.0.0",
|
|
85
91
|
"stylelint-config-standard-vue": "^1.0.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.view[data-v-4f9bcc52]{display:flex;flex-direction:column;height:100%;overflow:hidden;padding:12px;gap:10px}.stats-row[data-v-4f9bcc52]{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;flex-shrink:0}.toolbar[data-v-4f9bcc52]{display:flex;align-items:center;gap:6px;flex-shrink:0;flex-wrap:wrap}.split[data-v-4f9bcc52]{display:flex;gap:12px;flex:1;overflow:hidden;min-height:0}.table-wrap[data-v-4f9bcc52]{flex:1;overflow:auto;border:.5px solid var(--border);border-radius:var(--radius-lg)}.detail-panel[data-v-4f9bcc52]{width:280px;flex-shrink:0;display:flex;flex-direction:column;gap:8px;overflow:auto;border:.5px solid var(--border);border-radius:var(--radius-lg);padding:12px;background:var(--bg3)}.detail-empty[data-v-4f9bcc52]{width:280px;flex-shrink:0;display:flex;align-items:center;justify-content:center;color:var(--text3);font-size:12px;border:.5px dashed var(--border);border-radius:var(--radius-lg)}.detail-header[data-v-4f9bcc52]{display:flex;align-items:center;justify-content:space-between}.meta-grid[data-v-4f9bcc52]{display:grid;grid-template-columns:auto 1fr;gap:4px 12px;font-size:11px}.section-label[data-v-4f9bcc52]{font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:.4px;color:var(--text3);margin-top:6px;min-height:fit-content}.payload-box[data-v-4f9bcc52]{font-family:var(--mono);font-size:11px;color:var(--text2);background:var(--bg2);border-radius:var(--radius);padding:8px 10px;overflow:auto;white-space:pre;max-height:160px}.waterfall[data-v-4f9bcc52]{flex-shrink:0;background:var(--bg3);border:.5px solid var(--border);border-radius:var(--radius-lg);padding:10px 12px}.waterfall-header[data-v-4f9bcc52]{display:flex;align-items:center;justify-content:space-between;gap:8px}.waterfall-body[data-v-4f9bcc52]{margin-top:6px}.wf-row[data-v-4f9bcc52]{display:flex;align-items:center;gap:8px;margin-bottom:4px}.wf-track[data-v-4f9bcc52]{flex:1;position:relative;height:8px;background:var(--bg2);border-radius:2px;overflow:hidden}.wf-bar[data-v-4f9bcc52]{position:absolute;top:0;height:100%;border-radius:2px;opacity:.8}.view[data-v-0c07531b]{display:flex;flex-direction:column;height:100%;overflow:hidden;padding:12px;gap:10px}.toolbar[data-v-0c07531b]{display:flex;align-items:center;gap:6px;flex-shrink:0;flex-wrap:wrap}.split[data-v-0c07531b]{display:flex;gap:12px;flex:1;overflow:hidden;min-height:0}.graph-area[data-v-0c07531b]{flex:1;overflow:auto;border:.5px solid var(--border);border-radius:var(--radius-lg);padding:12px;background:var(--bg3)}.legend[data-v-0c07531b]{display:flex;align-items:center;gap:12px;font-size:11px;color:var(--text2);margin-bottom:12px}.canvas-stage[data-v-0c07531b]{display:flex;justify-content:center;align-items:flex-start;min-width:100%}.dot[data-v-0c07531b]{width:8px;height:8px;border-radius:50%;display:inline-block;margin-right:2px}.canvas-wrap[data-v-0c07531b]{position:relative}.edges-svg[data-v-0c07531b]{position:absolute;top:0;left:0;pointer-events:none}.edge[data-v-0c07531b]{stroke:var(--border);stroke-width:1.5}.graph-node[data-v-0c07531b]{position:absolute;display:flex;align-items:center;gap:7px;padding:0 10px;height:32px;border-radius:var(--radius);border:.5px solid var(--border);background:var(--bg3);cursor:pointer;transition:border-color .12s,background .12s;overflow:hidden;box-sizing:border-box;white-space:nowrap}.graph-node[data-v-0c07531b]:hover{border-color:var(--text3)}.graph-node.is-selected[data-v-0c07531b]{border-color:var(--node-color);background:color-mix(in srgb,var(--node-color) 8%,transparent)}.node-dot[data-v-0c07531b]{width:7px;height:7px;border-radius:50%;flex-shrink:0}.node-label[data-v-0c07531b]{font-size:11px;flex:1;overflow:hidden;text-overflow:ellipsis}.badge-xs[data-v-0c07531b]{font-size:9px;padding:1px 4px}.detail-panel[data-v-0c07531b]{width:280px;flex-shrink:0;overflow:auto;border:.5px solid var(--border);border-radius:var(--radius-lg);padding:12px;background:var(--bg3);display:flex;flex-direction:column;gap:4px;min-height:0}.detail-empty[data-v-0c07531b]{width:280px;display:flex;align-items:center;justify-content:center;color:var(--text3);font-size:12px;border:.5px dashed var(--border);border-radius:var(--radius-lg);flex-shrink:0}.graph-empty[data-v-0c07531b]{display:flex;align-items:center;justify-content:center;min-height:180px;color:var(--text3);font-size:12px}.detail-header[data-v-0c07531b]{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.section-label[data-v-0c07531b]{font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:.4px;color:var(--text3);margin:8px 0 5px}.detail-section[data-v-0c07531b]{display:flex;flex-direction:column;min-height:0}.detail-list[data-v-0c07531b]{display:flex;flex-direction:column;gap:3px;overflow:auto;max-height:220px;padding-right:2px}.provide-row[data-v-0c07531b]{display:flex;flex-direction:column;gap:4px;padding:5px 8px;background:var(--bg2);border-radius:var(--radius);margin-bottom:3px}.row-warning[data-v-0c07531b]{font-size:11px;color:var(--amber);padding:2px 0}.row-consumers[data-v-0c07531b]{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:2px 0}.consumer-chip[data-v-0c07531b]{font-size:10px;padding:1px 6px;border-radius:4px;background:color-mix(in srgb,var(--blue) 10%,var(--bg3));border:.5px solid color-mix(in srgb,var(--blue) 30%,var(--border));color:var(--text2)}.scope-badge[data-v-0c07531b]{font-size:10px;padding:1px 6px;border-radius:4px}.scope-global[data-v-0c07531b]{background:color-mix(in srgb,var(--amber) 15%,transparent);border:.5px solid color-mix(in srgb,var(--amber) 40%,var(--border));color:color-mix(in srgb,var(--amber) 80%,var(--text))}.scope-layout[data-v-0c07531b]{background:color-mix(in srgb,var(--purple) 15%,transparent);border:.5px solid color-mix(in srgb,var(--purple) 40%,var(--border));color:color-mix(in srgb,var(--purple) 80%,var(--text))}.scope-component[data-v-0c07531b]{background:var(--bg3);border:.5px solid var(--border);color:var(--text3)}.row-main[data-v-0c07531b]{display:flex;align-items:center;gap:8px;min-width:0}.row-key[data-v-0c07531b]{min-width:100px;color:var(--text2);flex-shrink:0}.row-value-preview[data-v-0c07531b]{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.row-toggle[data-v-0c07531b]{padding:2px 8px;font-size:10px}.value-box[data-v-0c07531b]{font-family:var(--mono);font-size:11px;color:var(--text2);background:#0000001a;border-radius:var(--radius);padding:8px 10px;white-space:pre-wrap;word-break:break-word;overflow:auto;max-height:180px}.inject-row[data-v-0c07531b]{display:flex;align-items:center;gap:8px;padding:5px 8px;background:var(--bg2);border-radius:var(--radius);margin-bottom:3px}.row-from[data-v-0c07531b]{margin-left:auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.inject-miss[data-v-0c07531b]{background:#e24b4a14}.jump-btn[data-v-0c07531b]{font-size:10px;padding:1px 6px;border:.5px solid var(--border);border-radius:var(--radius);background:transparent;color:var(--text3);cursor:pointer;flex-shrink:0;font-family:var(--mono)}.jump-btn[data-v-0c07531b]:hover{border-color:var(--teal);color:var(--teal);background:color-mix(in srgb,var(--teal) 8%,transparent)}.view[data-v-28f502da]{display:flex;flex-direction:column;height:100%;overflow:hidden;padding:12px;gap:10px}.stats-row[data-v-28f502da]{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;flex-shrink:0}.toolbar[data-v-28f502da]{display:flex;align-items:center;gap:6px;flex-shrink:0;flex-wrap:wrap}.clear-btn[data-v-28f502da]{color:var(--text3);border-color:var(--border);flex-shrink:0}.clear-btn[data-v-28f502da]:hover{color:var(--red);border-color:var(--red);background:transparent}.mode-btn[data-v-28f502da]{border-color:color-mix(in srgb,var(--blue) 40%,var(--border));color:var(--blue)}.mode-btn[data-v-28f502da]:hover{border-color:var(--blue);background:color-mix(in srgb,var(--blue) 12%,transparent)}.list[data-v-28f502da]{flex:1;overflow:auto;display:flex;flex-direction:column;gap:5px;min-height:0}.comp-card[data-v-28f502da]{background:var(--bg3);border:.5px solid var(--border);border-radius:var(--radius-lg);overflow:hidden;cursor:pointer;flex-shrink:0}.comp-card[data-v-28f502da]:hover{border-color:var(--text3)}.comp-card.leak[data-v-28f502da]{border-left:2px solid var(--red);border-radius:0 var(--radius-lg) var(--radius-lg) 0}.comp-card.expanded[data-v-28f502da]{border-color:var(--purple)}.comp-header[data-v-28f502da]{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;gap:8px}.comp-identity[data-v-28f502da]{display:flex;align-items:baseline;gap:6px;min-width:0;flex:1}.comp-name[data-v-28f502da]{font-size:12px;font-weight:500;color:var(--text);flex-shrink:0}.comp-file[data-v-28f502da]{font-size:11px;color:var(--text3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.comp-meta[data-v-28f502da]{display:flex;align-items:center;gap:5px;flex-shrink:0}.refs-preview[data-v-28f502da]{display:flex;flex-wrap:wrap;gap:4px;padding:0 12px 8px;align-items:center}.ref-chip[data-v-28f502da]{display:inline-flex;align-items:center;gap:4px;padding:2px 7px;border-radius:4px;background:var(--bg2);border:.5px solid var(--border);font-size:11px;font-family:var(--mono);max-width:220px;overflow:hidden}.ref-chip--reactive[data-v-28f502da]{border-color:color-mix(in srgb,var(--purple) 40%,var(--border));background:color-mix(in srgb,var(--purple) 8%,var(--bg2))}.ref-chip--computed[data-v-28f502da]{border-color:color-mix(in srgb,var(--blue) 40%,var(--border));background:color-mix(in srgb,var(--blue) 8%,var(--bg2))}.ref-chip-key[data-v-28f502da]{color:var(--text2);flex-shrink:0}.ref-chip-val[data-v-28f502da]{color:var(--teal);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.comp-detail[data-v-28f502da]{padding:4px 12px 12px;border-top:.5px solid var(--border);display:flex;flex-direction:column;gap:3px}.leak-banner[data-v-28f502da]{background:color-mix(in srgb,var(--red) 12%,transparent);border:.5px solid color-mix(in srgb,var(--red) 40%,var(--border));border-radius:var(--radius);padding:6px 10px;font-size:11px;color:var(--red);margin-bottom:6px;font-family:var(--mono)}.section-label[data-v-28f502da]{font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:.4px;color:var(--text3);margin-top:6px;margin-bottom:3px}.ref-row[data-v-28f502da]{display:flex;align-items:flex-start;gap:8px;padding:3px 0}.ref-key[data-v-28f502da]{min-width:90px;color:var(--text2);flex-shrink:0}.ref-val[data-v-28f502da]{flex:1;color:var(--teal);min-width:0}.ref-val--collapsed[data-v-28f502da]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ref-val--full[data-v-28f502da]{white-space:pre-wrap;word-break:break-all;line-height:1.5}.ref-row-actions[data-v-28f502da]{display:flex;align-items:center;gap:4px;flex-shrink:0}.expand-btn[data-v-28f502da]{font-size:9px;padding:1px 5px;border-radius:4px;border:.5px solid var(--border);background:var(--bg2);color:var(--text3);cursor:pointer;line-height:1.4;flex-shrink:0}.expand-btn[data-v-28f502da]:hover{border-color:var(--text3);color:var(--text)}.lc-row[data-v-28f502da]{display:flex;align-items:center;gap:8px;padding:2px 0}.lc-dot[data-v-28f502da]{width:6px;height:6px;border-radius:50%;flex-shrink:0}.ref-chip--shared[data-v-28f502da]{border-color:color-mix(in srgb,var(--amber) 50%,var(--border));background:color-mix(in srgb,var(--amber) 10%,var(--bg2))}.ref-chip-shared-dot[data-v-28f502da]{display:inline-block;width:5px;height:5px;border-radius:50%;background:var(--amber);flex-shrink:0;margin-left:1px}.global-banner[data-v-28f502da]{display:flex;align-items:flex-start;gap:8px;background:color-mix(in srgb,var(--amber) 10%,transparent);border:.5px solid color-mix(in srgb,var(--amber) 40%,var(--border));border-radius:var(--radius);padding:7px 10px;font-size:11px;color:var(--text2);margin-bottom:6px}.global-dot[data-v-28f502da]{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--amber);flex-shrink:0;margin-top:3px}.badge-amber[data-v-28f502da]{background:color-mix(in srgb,var(--amber) 15%,transparent);color:color-mix(in srgb,var(--amber) 80%,var(--text));border:.5px solid color-mix(in srgb,var(--amber) 40%,var(--border))}.history-list[data-v-28f502da]{display:flex;flex-direction:column;gap:1px;background:var(--bg2);border-radius:var(--radius);padding:4px 8px;max-height:180px;overflow-y:auto}.history-row[data-v-28f502da]{display:flex;align-items:center;gap:8px;padding:2px 0;font-size:11px;font-family:var(--mono);border-bottom:.5px solid var(--border)}.history-row[data-v-28f502da]:last-child{border-bottom:none}.history-time[data-v-28f502da]{min-width:52px;color:var(--text3);flex-shrink:0}.history-key[data-v-28f502da]{min-width:80px;color:var(--text2);flex-shrink:0}.history-val[data-v-28f502da]{color:var(--amber);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.stat-card[data-v-28f502da]{background:var(--bg3);border:.5px solid var(--border);border-radius:var(--radius-lg);padding:10px 14px}.stat-label[data-v-28f502da]{font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:.4px;color:var(--text3);margin-bottom:4px}.stat-val[data-v-28f502da]{font-size:22px;font-weight:500;line-height:1;color:var(--text)}.ref-key--clickable[data-v-28f502da]{cursor:pointer;text-decoration:underline dotted var(--text3);text-underline-offset:2px}.ref-key--clickable[data-v-28f502da]:hover{color:var(--purple);text-decoration-color:var(--purple)}.edit-btn[data-v-28f502da]{font-size:10px;padding:1px 6px;border-radius:var(--radius);border:.5px solid var(--border);background:transparent;color:var(--text3);cursor:pointer;margin-left:auto;flex-shrink:0;font-family:var(--mono)}.edit-btn[data-v-28f502da]:hover{border-color:var(--purple);color:var(--purple);background:color-mix(in srgb,var(--purple) 8%,transparent)}.lookup-panel[data-v-28f502da]{flex-shrink:0;border:.5px solid var(--border);border-radius:var(--radius-lg);background:var(--bg3);overflow:hidden}.lookup-header[data-v-28f502da]{display:flex;align-items:center;gap:6px;padding:7px 12px;border-bottom:.5px solid var(--border);background:var(--bg2)}.lookup-row[data-v-28f502da]{display:flex;align-items:center;gap:8px;padding:5px 12px;border-bottom:.5px solid var(--border)}.lookup-row[data-v-28f502da]:last-child{border-bottom:none}.edit-overlay[data-v-28f502da]{position:fixed;top:0;right:0;bottom:0;left:0;background:#0006;z-index:100;display:flex;align-items:center;justify-content:center}.edit-dialog[data-v-28f502da]{background:var(--bg1, var(--bg2));border:.5px solid var(--border);border-radius:var(--radius-lg);padding:14px 16px;width:380px;max-width:92vw;display:flex;flex-direction:column;gap:6px;box-shadow:0 8px 32px #0000004d}.edit-dialog-header[data-v-28f502da]{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text2);margin-bottom:2px}.edit-textarea[data-v-28f502da]{width:100%;font-family:var(--mono);font-size:12px;padding:8px 10px;background:var(--bg2);border:.5px solid var(--border);border-radius:var(--radius);color:var(--text);resize:vertical;outline:none}.edit-textarea[data-v-28f502da]:focus{border-color:var(--purple)}.edit-error[data-v-28f502da]{color:var(--red);font-family:var(--mono)}.edit-actions[data-v-28f502da]{display:flex;gap:6px;padding-top:4px}.slide-enter-active[data-v-28f502da],.slide-leave-active[data-v-28f502da]{transition:opacity .15s,transform .15s}.slide-enter-from[data-v-28f502da],.slide-leave-to[data-v-28f502da]{opacity:0;transform:translateY(6px)}.fade-enter-active[data-v-28f502da],.fade-leave-active[data-v-28f502da]{transition:opacity .15s}.fade-enter-from[data-v-28f502da],.fade-leave-to[data-v-28f502da]{opacity:0}.jump-btn[data-v-28f502da]{font-size:10px;padding:1px 6px;border:.5px solid var(--border);border-radius:var(--radius);background:transparent;color:var(--text3);cursor:pointer;flex-shrink:0;font-family:var(--mono)}.jump-btn[data-v-28f502da]:hover{border-color:var(--teal);color:var(--teal);background:color-mix(in srgb,var(--teal) 8%,transparent)}.view[data-v-a5a04da1]{display:flex;flex-direction:column;height:100%;overflow:hidden;padding:12px;gap:10px}.controls[data-v-a5a04da1]{display:flex;align-items:center;gap:8px;flex-shrink:0;flex-wrap:wrap}.mode-group[data-v-a5a04da1]{display:flex;gap:2px}.threshold-group[data-v-a5a04da1]{display:flex;align-items:center;gap:6px}.stats-row[data-v-a5a04da1]{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;flex-shrink:0}.stat-sub[data-v-a5a04da1]{margin-top:4px;font-size:11px;color:var(--text3)}.inspector[data-v-a5a04da1]{display:grid;grid-template-columns:minmax(220px,280px) minmax(0,1fr) minmax(260px,320px);gap:12px;flex:1;min-height:0}.roots-panel[data-v-a5a04da1],.tree-panel[data-v-a5a04da1],.detail-panel[data-v-a5a04da1]{border:.5px solid var(--border);border-radius:var(--radius-lg);background:var(--bg3);min-height:0}.roots-panel[data-v-a5a04da1],.detail-panel[data-v-a5a04da1]{display:flex;flex-direction:column;overflow:auto;padding:12px;gap:8px}.panel-title[data-v-a5a04da1]{font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:.4px;color:var(--text3)}.root-item[data-v-a5a04da1]{display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg2);color:var(--text);text-align:left}.root-item.active[data-v-a5a04da1]{border-color:var(--teal);background:color-mix(in srgb,var(--teal) 16%,var(--bg2))}.root-label[data-v-a5a04da1]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.root-copy[data-v-a5a04da1]{display:flex;flex-direction:column;min-width:0}.root-sub[data-v-a5a04da1]{font-size:11px}.root-meta[data-v-a5a04da1]{color:var(--text3);font-size:11px}.tree-panel[data-v-a5a04da1]{display:flex;flex-direction:column;overflow:hidden}.tree-toolbar[data-v-a5a04da1]{padding:12px;border-bottom:.5px solid var(--border)}.search-input[data-v-a5a04da1]{width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg2);color:var(--text)}.tree-frame[data-v-a5a04da1]{flex:1;min-height:0;overflow:auto;padding:12px}[data-v-a5a04da1] .tree-canvas{display:inline-block;min-width:100%;width:max-content}[data-v-a5a04da1] .tree-node{margin-bottom:4px}[data-v-a5a04da1] .tree-row{display:grid;grid-template-columns:8px 18px minmax(0,1fr) auto;align-items:center;gap:6px;min-width:0;width:100%;padding:4px 8px;padding-left:calc(8px + (var(--tree-depth, 0) * 16px));border:1px solid transparent;border-radius:var(--radius);cursor:pointer;white-space:nowrap}[data-v-a5a04da1] .tree-row:hover{background:var(--bg2)}[data-v-a5a04da1] .tree-row.selected{background:color-mix(in srgb,var(--teal) 12%,var(--bg2));border-color:var(--teal)}[data-v-a5a04da1] .tree-row.hot{box-shadow:inset 2px 0 0 var(--red)}[data-v-a5a04da1] .tree-toggle{width:16px;height:16px;border:none;background:transparent;color:var(--text3);padding:0;font-size:14px;display:inline-flex;align-items:center;justify-content:center}[data-v-a5a04da1] .tree-toggle:disabled{cursor:default}[data-v-a5a04da1] .tree-toggle.empty{opacity:0}[data-v-a5a04da1] .tree-rail{display:block;width:2px;height:14px;border-radius:999px;background:color-mix(in srgb,var(--border) 75%,transparent)}[data-v-a5a04da1] .tree-copy{display:flex;align-items:center;min-width:0;gap:6px;overflow:hidden}[data-v-a5a04da1] .tree-name{font-size:12px;color:var(--text);min-width:0;overflow:hidden;text-overflow:ellipsis}[data-v-a5a04da1] .tree-badges{display:flex;gap:6px;flex-shrink:0;overflow:hidden}[data-v-a5a04da1] .tree-badge{border:1px solid var(--border);border-radius:999px;padding:2px 7px;font-size:10px;color:var(--text3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:160px}[data-v-a5a04da1] .tree-metrics{display:flex;align-items:center;min-width:92px;justify-content:flex-end;flex-shrink:0;gap:6px}[data-v-a5a04da1] .tree-metric-pill{display:inline-flex;align-items:center;justify-content:center;min-width:78px;padding:2px 8px;border:1px solid var(--border);border-radius:999px;background:var(--bg2);font-size:10px;color:var(--text3)}[data-v-a5a04da1] .tree-persistent-pill{display:inline-flex;align-items:center;padding:2px 8px;border:1px solid color-mix(in srgb,var(--amber) 55%,var(--border));border-radius:999px;background:color-mix(in srgb,var(--amber) 10%,var(--bg2));font-size:10px;color:color-mix(in srgb,var(--amber) 80%,var(--text))}[data-v-a5a04da1] .tree-hydration-pill{display:inline-flex;align-items:center;padding:2px 8px;border:1px solid color-mix(in srgb,var(--teal) 55%,var(--border));border-radius:999px;background:color-mix(in srgb,var(--teal) 10%,var(--bg2));font-size:10px;color:color-mix(in srgb,var(--teal) 80%,var(--text))}[data-v-a5a04da1] .tree-children{margin-left:7px;padding-left:11px;border-left:1px solid color-mix(in srgb,var(--border) 72%,transparent)}.detail-empty[data-v-a5a04da1]{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text3);font-size:12px}.detail-header[data-v-a5a04da1]{display:flex;align-items:center;justify-content:space-between}.meta-grid[data-v-a5a04da1]{display:grid;grid-template-columns:auto 1fr;gap:4px 12px}.detail-pill-row[data-v-a5a04da1]{display:flex;flex-wrap:wrap;gap:6px}.detail-pill[data-v-a5a04da1]{border:1px solid var(--border);border-radius:999px;padding:4px 8px;background:var(--bg2);font-size:11px}.detail-pill.hot[data-v-a5a04da1]{border-color:color-mix(in srgb,var(--red) 50%,var(--border));color:var(--red)}.detail-pill.persistent[data-v-a5a04da1]{border-color:color-mix(in srgb,var(--amber) 55%,var(--border));color:color-mix(in srgb,var(--amber) 80%,var(--text))}.detail-pill.hydrated[data-v-a5a04da1]{border-color:color-mix(in srgb,var(--teal) 55%,var(--border));color:color-mix(in srgb,var(--teal) 80%,var(--text))}.detail-pill.muted[data-v-a5a04da1]{color:var(--text3);border-color:var(--border)}.section-label[data-v-a5a04da1]{font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:.4px;color:var(--text3);margin-top:8px;margin-bottom:4px}.trigger-item[data-v-a5a04da1]{background:var(--bg2);border-radius:var(--radius);padding:4px 8px;margin-bottom:3px;color:var(--text2)}[data-v-a5a04da1] .tree-jump-btn{display:none;padding:0 4px;border:none;background:transparent;color:var(--text3);font-size:11px;cursor:pointer;line-height:1;flex-shrink:0}[data-v-a5a04da1] .tree-row:hover .tree-jump-btn,[data-v-a5a04da1] .tree-row.selected .tree-jump-btn{display:inline-flex}[data-v-a5a04da1] .tree-jump-btn:hover{color:var(--teal)}.jump-btn[data-v-a5a04da1]{font-size:10px;padding:1px 6px;border:.5px solid var(--border);border-radius:var(--radius);background:transparent;color:var(--text3);cursor:pointer;flex-shrink:0;font-family:var(--mono)}.jump-btn[data-v-a5a04da1]:hover{border-color:var(--teal);color:var(--teal);background:color-mix(in srgb,var(--teal) 8%,transparent)}.route-select[data-v-a5a04da1]{padding:3px 7px;border:.5px solid var(--border);border-radius:var(--radius);background:var(--bg2);color:var(--text);font-size:11px;cursor:pointer;max-width:140px}.timeline-list[data-v-a5a04da1]{display:flex;flex-direction:column;gap:1px;background:var(--bg2);border-radius:var(--radius);padding:4px 8px;max-height:200px;overflow-y:auto;min-height:fit-content}.timeline-row[data-v-a5a04da1]{display:flex;align-items:center;gap:6px;padding:2px 0;font-size:11px;border-bottom:.5px solid var(--border);min-width:0;min-height:fit-content}.timeline-row[data-v-a5a04da1]:last-child{border-bottom:none}.timeline-kind[data-v-a5a04da1]{flex-shrink:0;font-size:10px;font-weight:500;min-width:40px}.timeline-kind.mount[data-v-a5a04da1]{color:var(--teal)}.timeline-kind.update[data-v-a5a04da1]{color:var(--amber)}.timeline-time[data-v-a5a04da1]{flex-shrink:0;min-width:52px;color:var(--text3)}.timeline-dur[data-v-a5a04da1]{flex-shrink:0;min-width:38px;color:var(--text2)}.timeline-trigger[data-v-a5a04da1]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text3);flex:1;min-width:0}.timeline-route[data-v-a5a04da1]{flex-shrink:0;color:var(--text3);font-size:10px}@media(max-width:1180px){.inspector[data-v-a5a04da1]{grid-template-columns:minmax(200px,240px) minmax(0,1fr)}.detail-panel[data-v-a5a04da1]{grid-column:1 / -1;max-height:220px}}.timeline-root[data-v-da869dac]{display:flex;flex-direction:column;height:100%;overflow:hidden}.stats-row[data-v-da869dac]{display:flex;gap:10px;padding:12px 14px 0;flex-shrink:0}.stat-card[data-v-da869dac]{background:var(--bg2);border:.5px solid var(--border);border-radius:var(--radius);padding:8px 14px;min-width:72px;text-align:center}.stat-val[data-v-da869dac]{font-size:20px;font-weight:600;font-family:var(--mono);line-height:1.1}.stat-unit[data-v-da869dac]{font-size:12px;opacity:.6;margin-left:1px}.stat-label[data-v-da869dac]{font-size:10px;color:var(--text3);margin-top:2px;text-transform:uppercase;letter-spacing:.4px}.toolbar[data-v-da869dac]{display:flex;align-items:center;gap:8px;padding:10px 14px;flex-shrink:0;border-bottom:.5px solid var(--border)}.search-input[data-v-da869dac]{flex:1;max-width:260px}.filter-group[data-v-da869dac]{display:flex;gap:4px}.content-area[data-v-da869dac]{display:flex;flex:1;overflow:hidden;min-height:0}.table-pane[data-v-da869dac]{flex:1;overflow:hidden auto;min-width:0}.bar-cell[data-v-da869dac]{width:200px;padding:4px 8px}.bar-track[data-v-da869dac]{position:relative;height:8px;background:var(--bg2);border-radius:4px;overflow:hidden}.bar-fill[data-v-da869dac]{position:absolute;top:0;height:100%;min-width:3px;border-radius:4px;transition:width .15s}.detail-panel[data-v-da869dac]{width:260px;flex-shrink:0;border-left:.5px solid var(--border);overflow-y:auto;background:var(--bg3);padding:0 0 16px}.panel-header[data-v-da869dac]{display:flex;align-items:center;justify-content:space-between;padding:10px 14px 8px;border-bottom:.5px solid var(--border);position:sticky;top:0;background:var(--bg3);z-index:1}.panel-title[data-v-da869dac]{font-family:var(--mono);font-size:13px;font-weight:500}.close-btn[data-v-da869dac]{border:none;background:transparent;color:var(--text3);font-size:11px;padding:2px 6px;cursor:pointer}.panel-section[data-v-da869dac]{padding:10px 14px 6px;border-bottom:.5px solid var(--border)}.panel-section-title[data-v-da869dac]{font-size:10px;font-weight:500;color:var(--text3);text-transform:uppercase;letter-spacing:.4px;margin-bottom:8px}.panel-row[data-v-da869dac]{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:3px 0;font-size:12px}.panel-key[data-v-da869dac]{color:var(--text3);flex-shrink:0}.panel-val[data-v-da869dac]{color:var(--text);text-align:right;word-break:break-all}.cancel-notice[data-v-da869dac],.active-notice[data-v-da869dac]{margin:10px 14px 0;font-size:11px;line-height:1.6;padding:8px 10px;border-radius:var(--radius)}.cancel-notice[data-v-da869dac]{background:#e24b4a1a;color:var(--red);border:.5px solid rgb(226 75 74 / 30%)}.active-notice[data-v-da869dac]{background:#7f77dd1a;color:var(--purple);border:.5px solid rgb(127 119 221 / 30%)}code[data-v-da869dac]{font-family:var(--mono);font-size:10px;background:#00000026;padding:1px 4px;border-radius:3px}.panel-slide-enter-active[data-v-da869dac],.panel-slide-leave-active[data-v-da869dac]{transition:transform .18s ease,opacity .18s ease}.panel-slide-enter-from[data-v-da869dac],.panel-slide-leave-to[data-v-da869dac]{transform:translate(12px);opacity:0}#app-root[data-v-12fe13bc]{display:flex;flex-direction:column;height:100vh;overflow:hidden}.tabbar[data-v-12fe13bc]{display:flex;align-items:center;gap:2px;padding:8px 12px 0;border-bottom:.5px solid var(--border);background:var(--bg3);flex-shrink:0}.tabbar-brand[data-v-12fe13bc]{font-size:11px;font-weight:500;color:var(--purple);letter-spacing:.5px;margin-right:12px;padding-bottom:8px}.tab-btn[data-v-12fe13bc]{border:none;border-bottom:2px solid transparent;border-radius:0;background:transparent;color:var(--text3);font-size:12px;padding:6px 12px 8px;cursor:pointer;transition:color .12s,border-color .12s;display:flex;align-items:center;gap:5px}.tab-btn[data-v-12fe13bc]:hover{color:var(--text);background:transparent}.tab-btn.active[data-v-12fe13bc]{color:var(--purple);border-bottom-color:var(--purple)}.tab-icon[data-v-12fe13bc]{font-size:10px;opacity:.6}.tab-content[data-v-12fe13bc]{flex:1;overflow:hidden;display:flex;flex-direction:column}*{box-sizing:border-box;margin:0;padding:0}body{font-family:var(--font);background:var(--bg);color:var(--text);font-size:13px;line-height:1.5}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}button{font-family:var(--font);font-size:12px;cursor:pointer;border:.5px solid var(--border);background:transparent;color:var(--text2);padding:4px 10px;border-radius:var(--radius);transition:background .12s}button:hover{background:var(--bg2)}button:active{transform:scale(.98)}button.active{background:#7f77dd26;color:var(--purple);border-color:var(--purple)}button.danger-active{background:#e24b4a1f;color:var(--red);border-color:var(--red)}button.success-active{background:#1d9e751f;color:var(--teal);border-color:var(--teal)}input[type=text],input[type=search]{font-family:var(--font);font-size:12px;border:.5px solid var(--border);background:var(--bg2);color:var(--text);padding:5px 10px;border-radius:var(--radius);outline:none;width:100%}input[type=text]:focus,input[type=search]:focus{border-color:var(--purple);box-shadow:0 0 0 2px #7f77dd33}input[type=range]{accent-color:var(--purple);cursor:pointer}.mono{font-family:var(--mono)}.muted{color:var(--text3)}.text-sm{font-size:11px}.text-xs{font-size:10px}.bold{font-weight:500}.badge{display:inline-block;font-size:10px;font-weight:500;padding:2px 7px;border-radius:99px;white-space:nowrap}.badge-ok{background:#1d9e7526;color:var(--teal)}.badge-err{background:#e24b4a1f;color:var(--red)}.badge-warn{background:#ef9f2726;color:var(--amber)}.badge-info{background:#378add1f;color:var(--blue)}.badge-gray{background:var(--bg2);color:var(--text3);border:.5px solid var(--border)}.badge-purple{background:#7f77dd26;color:var(--purple)}.card{background:var(--bg3);border:.5px solid var(--border);border-radius:var(--radius-lg);padding:12px 14px}.data-table{width:100%;border-collapse:collapse;font-size:12px}.data-table th{text-align:left;font-size:10px;font-weight:500;color:var(--text3);padding:6px 8px;border-bottom:.5px solid var(--border);text-transform:uppercase;letter-spacing:.4px;white-space:nowrap}.data-table td{padding:8px;border-bottom:.5px solid var(--border);color:var(--text);vertical-align:middle}.data-table tr:hover td{background:var(--bg2);cursor:pointer}.data-table tr.selected td{background:#7f77dd14}.stat-card{background:var(--bg2);border-radius:var(--radius);padding:10px 12px}.stat-label{font-size:10px;color:var(--text3);text-transform:uppercase;letter-spacing:.4px;margin-bottom:3px}.stat-val{font-size:20px;font-weight:500}.flex{display:flex}.items-center{align-items:center}.gap-2{gap:8px}.gap-3{gap:12px}.flex-1{flex:1}.overflow-auto{overflow:auto}.p-3{padding:12px}.p-4{padding:16px}.mb-2{margin-bottom:8px}.mb-3{margin-bottom:12px}.mt-2{margin-top:8px}
|