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/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 CLIENT_PORT = 4949;
630
- const clientOrigin = `http://localhost:${CLIENT_PORT}`;
631
- let innerServer = null;
632
- nuxt.hook("vite:serverCreated", async (_viteServer, env) => {
633
- if (!env.isClient) {
634
- return;
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
- if (innerServer) {
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
- const { createServer } = await import('vite');
640
- const { default: vue } = await import('@vitejs/plugin-vue');
641
- innerServer = await createServer({
642
- root: resolver.resolve("../client"),
643
- base: "/",
644
- server: { port: CLIENT_PORT, strictPort: true, cors: true },
645
- appType: "spa",
646
- configFile: false,
647
- plugins: [vue()],
648
- logLevel: "warn"
649
- });
650
- await innerServer.listen();
651
- nuxt.hook("close", () => {
652
- innerServer?.close();
653
- innerServer = null;
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
- const base = clientOrigin;
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
  }
@@ -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
- let lastMessageSourceRef = null;
40
- let lastMessageOrigin = "";
41
- const messageHandler = (event) => {
42
- if (config.clientOrigin && event.origin !== config.clientOrigin) {
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 type = event.data?.type;
46
- if (type === "observatory:request") {
47
- lastMessageSourceRef = event.source ? new WeakRef(event.source) : null;
48
- lastMessageOrigin = event.origin;
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 (type === "observatory:clear-composables") {
63
+ if (payload.cmd === "clear-composables") {
64
+ debugLog("received command: clear-composables");
54
65
  if (composableRegistry) {
55
- composableRegistry.clear();
66
+ if (composableNavigationMode === "session") {
67
+ composableRegistry.clearNonLayout();
68
+ } else {
69
+ composableRegistry.clear();
70
+ }
56
71
  }
57
- const source = event.source;
58
- source?.postMessage({ type: "observatory:snapshot", data: buildSnapshot() }, event.origin);
72
+ broadcastAll("command:clear-composables");
59
73
  return;
60
74
  }
61
- if (type === "observatory:set-composable-mode") {
62
- const mode = event.data?.mode;
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 (type === "observatory:open-in-editor") {
76
- const { file } = event.data;
77
- if (file && file !== "unknown") {
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
- window.removeEventListener("message", messageHandler);
87
- lastMessageSourceRef = null;
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") render.reset();
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
- const channel = getDevtoolsChannel();
148
- if (!channel) {
169
+ if (!import.meta.hot) {
149
170
  return;
150
171
  }
151
- channel.send("observatory:snapshot", buildSnapshot());
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.25",
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 '.nuxt/**' --ignore-pattern '.output/**' --ignore-pattern 'coverage/**' --ignore-pattern 'client/dist/**' --fix && eslint --fix '**/*.{ts,vue,js}' --ignore-pattern 'coverage/**' --ignore-pattern 'client/dist/**'",
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}