@presto1314w/vite-devtools-browser 0.3.1 → 0.3.3

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 CHANGED
@@ -271,12 +271,15 @@ vite-browser eval <script>
271
271
 
272
272
  ## Current Boundaries
273
273
 
274
- `v0.3.1` is strong at:
274
+ `v0.3.3` is strong at:
275
275
  - surfacing runtime state as structured shell output
276
276
  - linking current errors to recent HMR and module activity
277
277
  - detecting common HMR failure patterns with confidence levels
278
278
  - narrowing likely store/module -> render paths in Vue-first flows
279
279
  - capturing browser-side runtime errors even when the Vite overlay is absent
280
+ - correlating sparse HMR traces back to the right source module more reliably
281
+ - retaining `changedKeys` and source-module hints when one side of the event stream is incomplete
282
+ - clearing stale runtime errors after recovery so the current page lifecycle is easier to trust
280
283
 
281
284
  `correlate renders` and `diagnose propagation` are **high-confidence propagation clues**, not strict causal proof. They do not reliably trace deep chains like `store -> component A -> component B -> error` across arbitrary graphs, and intentionally fall back to conservative output when evidence is incomplete.
282
285
 
@@ -1,13 +1,15 @@
1
1
  export function initBrowserEventCollector() {
2
- if (window.__vb_events)
3
- return;
4
- window.__vb_events = [];
5
- window.__vb_push = (event) => {
6
- const q = window.__vb_events;
7
- q.push(event);
8
- if (q.length > 1000)
9
- q.shift();
10
- };
2
+ if (!window.__vb_events) {
3
+ window.__vb_events = [];
4
+ }
5
+ if (!window.__vb_push) {
6
+ window.__vb_push = (event) => {
7
+ const q = window.__vb_events;
8
+ q.push(event);
9
+ if (q.length > 1000)
10
+ q.shift();
11
+ };
12
+ }
11
13
  const inferFramework = () => {
12
14
  if (window.__VUE__ || window.__VUE_DEVTOOLS_GLOBAL_HOOK__)
13
15
  return "vue";
@@ -117,15 +119,16 @@ export function initBrowserEventCollector() {
117
119
  }, 60);
118
120
  };
119
121
  const attachPiniaSubscriptions = () => {
120
- if (window.__vb_pinia_attached)
121
- return;
122
122
  const hook = window.__VUE_DEVTOOLS_GLOBAL_HOOK__;
123
123
  const app = Array.isArray(hook?.apps) ? hook.apps[0] : null;
124
124
  const pinia = window.__PINIA__ || window.pinia || app?.config?.globalProperties?.$pinia;
125
125
  const registry = pinia?._s;
126
126
  if (!(registry instanceof Map) || registry.size === 0)
127
127
  return;
128
- const attached = (window.__vb_pinia_attached = new Set());
128
+ const attached = window.__vb_pinia_attached ||
129
+ (window.__vb_pinia_attached = new Set());
130
+ const actionAttached = window.__vb_pinia_action_attached ||
131
+ (window.__vb_pinia_action_attached = new Set());
129
132
  registry.forEach((store, storeId) => {
130
133
  if (!store || typeof store.$subscribe !== "function" || attached.has(String(storeId)))
131
134
  return;
@@ -161,10 +164,33 @@ export function initBrowserEventCollector() {
161
164
  },
162
165
  });
163
166
  scheduleRender("store-update", { changedKeys });
164
- }, { detached: true });
167
+ },
168
+ // Flush synchronously so the store event is recorded before a render-time failure
169
+ // can short-circuit the rest of Vue's update cycle.
170
+ { detached: true, flush: "sync" });
171
+ if (typeof store.$onAction === "function" && !actionAttached.has(String(storeId))) {
172
+ actionAttached.add(String(storeId));
173
+ store.$onAction(({ name, after }) => {
174
+ after(() => {
175
+ window.__vb_push({
176
+ timestamp: Date.now(),
177
+ type: "store-update",
178
+ payload: {
179
+ store: String(storeId),
180
+ mutationType: typeof name === "string" && name.length > 0 ? `action:${name}` : "action",
181
+ events: 0,
182
+ changedKeys: [],
183
+ },
184
+ });
185
+ scheduleRender("store-update");
186
+ });
187
+ }, true);
188
+ }
165
189
  });
166
190
  };
167
191
  function attachViteListener() {
192
+ if (window.__vb_vite_listener_attached)
193
+ return true;
168
194
  const hot = window.__vite_hot;
169
195
  if (hot?.ws) {
170
196
  hot.ws.addEventListener("message", (e) => {
@@ -181,6 +207,7 @@ export function initBrowserEventCollector() {
181
207
  }
182
208
  catch { }
183
209
  });
210
+ window.__vb_vite_listener_attached = true;
184
211
  return true;
185
212
  }
186
213
  return false;
@@ -194,22 +221,28 @@ export function initBrowserEventCollector() {
194
221
  }
195
222
  }, 100);
196
223
  }
197
- const origOnError = window.onerror;
198
- window.onerror = (msg, src, line, col, err) => {
199
- window.__vb_push({
200
- timestamp: Date.now(),
201
- type: "error",
202
- payload: { message: String(msg), source: src, line, col, stack: err?.stack },
203
- });
204
- return origOnError ? origOnError(msg, src, line, col, err) : false;
205
- };
206
- window.addEventListener("unhandledrejection", (e) => {
207
- window.__vb_push({
208
- timestamp: Date.now(),
209
- type: "error",
210
- payload: { message: e.reason?.message, stack: e.reason?.stack },
224
+ if (!window.__vb_onerror_attached) {
225
+ const origOnError = window.onerror;
226
+ window.onerror = (msg, src, line, col, err) => {
227
+ window.__vb_push({
228
+ timestamp: Date.now(),
229
+ type: "error",
230
+ payload: { message: String(msg), source: src, line, col, stack: err?.stack },
231
+ });
232
+ return origOnError ? origOnError(msg, src, line, col, err) : false;
233
+ };
234
+ window.__vb_onerror_attached = true;
235
+ }
236
+ if (!window.__vb_unhandledrejection_attached) {
237
+ window.addEventListener("unhandledrejection", (e) => {
238
+ window.__vb_push({
239
+ timestamp: Date.now(),
240
+ type: "error",
241
+ payload: { message: e.reason?.message, stack: e.reason?.stack },
242
+ });
211
243
  });
212
- });
244
+ window.__vb_unhandledrejection_attached = true;
245
+ }
213
246
  const observeDom = () => {
214
247
  const root = document.body || document.documentElement;
215
248
  if (!root || window.__vb_render_observer)
@@ -246,7 +279,9 @@ export function initBrowserEventCollector() {
246
279
  observeDom();
247
280
  patchHistory();
248
281
  attachPiniaSubscriptions();
249
- window.setInterval(attachPiniaSubscriptions, 1000);
282
+ if (!window.__vb_pinia_retry_timer) {
283
+ window.__vb_pinia_retry_timer = window.setInterval(attachPiniaSubscriptions, 250);
284
+ }
250
285
  scheduleRender("initial-load");
251
286
  }
252
287
  export function readBrowserEvents() {
package/dist/browser.d.ts CHANGED
@@ -7,6 +7,17 @@ export { formatHmrTrace, formatModuleGraphSnapshot, formatModuleGraphTrace, form
7
7
  export type ModuleGraphMode = "snapshot" | "trace" | "clear";
8
8
  export declare function setEventQueue(queue: EventQueue): void;
9
9
  export declare function getEventQueue(): EventQueue | null;
10
+ export declare function getTrackedHmrEvents(windowMs?: number): Array<{
11
+ timestamp: number;
12
+ type: "hmr-update" | "hmr-error";
13
+ payload: {
14
+ path?: string;
15
+ message?: string;
16
+ updates?: Array<{
17
+ path?: string;
18
+ }>;
19
+ };
20
+ }>;
10
21
  export declare function getCurrentPage(): Page | null;
11
22
  /**
12
23
  * Flush browser events into daemon event queue
package/dist/browser.js CHANGED
@@ -19,6 +19,20 @@ export function setEventQueue(queue) {
19
19
  export function getEventQueue() {
20
20
  return eventQueue;
21
21
  }
22
+ export function getTrackedHmrEvents(windowMs = 5000) {
23
+ const cutoff = Date.now() - windowMs;
24
+ return session.hmrEvents
25
+ .filter((event) => event.timestamp >= cutoff)
26
+ .map((event) => ({
27
+ timestamp: event.timestamp,
28
+ type: event.type === "error" ? "hmr-error" : "hmr-update",
29
+ payload: {
30
+ path: event.path,
31
+ message: event.message,
32
+ updates: event.path ? [{ path: event.path }] : [],
33
+ },
34
+ }));
35
+ }
22
36
  export function getCurrentPage() {
23
37
  return getSessionPage(session);
24
38
  }
@@ -36,6 +50,7 @@ async function injectEventCollector(currentPage) {
36
50
  * Flush browser events into daemon event queue
37
51
  */
38
52
  export async function flushBrowserEvents(currentPage, queue) {
53
+ await currentPage.evaluate(initBrowserEventCollector);
39
54
  const raw = await currentPage.evaluate(readBrowserEvents);
40
55
  for (const e of raw) {
41
56
  queue.push(e);
@@ -149,7 +164,7 @@ export async function goto(url) {
149
164
  }
150
165
  export async function back() {
151
166
  const currentPage = await ensurePage();
152
- await currentPage.goBack({ waitUntil: "domcontentloaded" });
167
+ await navigateAndRefreshContext(currentPage, () => currentPage.goBack({ waitUntil: "domcontentloaded" }));
153
168
  }
154
169
  export async function reload() {
155
170
  const currentPage = await ensurePage();
@@ -275,6 +290,7 @@ export async function screenshot() {
275
290
  }
276
291
  export async function evaluate(script) {
277
292
  const currentPage = requireCurrentPage();
293
+ await currentPage.evaluate(initBrowserEventCollector);
278
294
  const result = await currentPage.evaluate(script);
279
295
  return JSON.stringify(result, null, 2);
280
296
  }
@@ -315,8 +331,12 @@ function requireCurrentPage() {
315
331
  }
316
332
  async function navigateAndRefreshContext(currentPage, navigate, refreshFramework = false) {
317
333
  await navigate();
334
+ clearRuntimeErrors();
318
335
  await injectEventCollector(currentPage);
319
336
  if (refreshFramework) {
320
337
  await detectFramework();
321
338
  }
322
339
  }
340
+ function clearRuntimeErrors() {
341
+ session.runtimeErrors.length = 0;
342
+ }
package/dist/cli.js CHANGED
@@ -1,6 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync } from "node:fs";
3
+ import { realpathSync } from "node:fs";
4
+ import { resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
3
6
  import { send } from "./client.js";
7
+ class CliExit extends Error {
8
+ code;
9
+ constructor(code) {
10
+ super(`CLI_EXIT:${code}`);
11
+ this.code = code;
12
+ }
13
+ }
4
14
  export function normalizeUrl(value) {
5
15
  if (value.includes("://"))
6
16
  return value;
@@ -255,16 +265,36 @@ OPTIONS
255
265
  `;
256
266
  }
257
267
  function isEntrypoint(argv1) {
258
- return Boolean(argv1 && import.meta.url.endsWith(argv1.replaceAll("\\", "/")));
268
+ if (!argv1)
269
+ return false;
270
+ try {
271
+ const current = realpathSync(fileURLToPath(import.meta.url));
272
+ const target = realpathSync(resolve(argv1));
273
+ return current === target;
274
+ }
275
+ catch {
276
+ return Boolean(argv1 && import.meta.url.endsWith(argv1.replaceAll("\\", "/")));
277
+ }
259
278
  }
260
279
  async function main() {
261
- await runCli(process.argv, {
262
- send,
263
- readFile: readFileSync,
264
- stdout: (text) => console.log(text),
265
- stderr: (text) => console.error(text),
266
- exit: (code) => process.exit(code),
267
- });
280
+ try {
281
+ await runCli(process.argv, {
282
+ send,
283
+ readFile: readFileSync,
284
+ stdout: (text) => process.stdout.write(`${text}\n`),
285
+ stderr: (text) => process.stderr.write(`${text}\n`),
286
+ exit: ((code) => {
287
+ throw new CliExit(code);
288
+ }),
289
+ });
290
+ }
291
+ catch (error) {
292
+ if (error instanceof CliExit) {
293
+ process.exitCode = error.code;
294
+ return;
295
+ }
296
+ throw error;
297
+ }
268
298
  }
269
299
  if (isEntrypoint(process.argv[1])) {
270
300
  void main().catch((error) => {
package/dist/daemon.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import { createServer } from "node:net";
2
2
  import { mkdirSync, writeFileSync, rmSync } from "node:fs";
3
+ import { setTimeout as sleep } from "node:timers/promises";
3
4
  import { fileURLToPath } from "node:url";
4
5
  import * as browser from "./browser.js";
5
6
  import { correlateErrorWithHMR, formatErrorCorrelationReport } from "./correlate.js";
6
7
  import { diagnoseHMR, formatDiagnosisReport } from "./diagnose.js";
7
8
  import { diagnosePropagation, formatPropagationDiagnosisReport } from "./diagnose-propagation.js";
9
+ import { extractModules } from "./event-analysis.js";
8
10
  import { socketDir, socketPath, pidFile } from "./paths.js";
9
11
  import { correlateRenderPropagation, formatPropagationTraceReport } from "./trace.js";
10
12
  import { EventQueue } from "./event-queue.js";
@@ -18,19 +20,8 @@ export function cleanError(err) {
18
20
  }
19
21
  export function createRunner(api = browser) {
20
22
  return async function run(cmd) {
21
- // Flush browser events to daemon queue before processing command
22
23
  const queue = api.getEventQueue();
23
- if (queue) {
24
- try {
25
- const currentPage = api.getCurrentPage();
26
- if (currentPage) {
27
- await api.flushBrowserEvents(currentPage, queue);
28
- }
29
- }
30
- catch {
31
- // Ignore flush errors (page might not be open yet)
32
- }
33
- }
24
+ await flushCurrentPageEvents(api, queue);
34
25
  // Browser control
35
26
  if (cmd.action === "open") {
36
27
  await api.open(cmd.url);
@@ -110,12 +101,20 @@ export function createRunner(api = browser) {
110
101
  if (cmd.action === "correlate-errors") {
111
102
  const errorText = String(await api.errors(Boolean(cmd.mapped), Boolean(cmd.inlineSource)));
112
103
  const events = queue ? queue.window(cmd.windowMs ?? 5000) : [];
113
- const data = formatErrorCorrelationReport(errorText, errorText === "no errors" ? null : correlateErrorWithHMR(errorText, events, cmd.windowMs ?? 5000));
104
+ const fallbackHmrEvents = readTrackedHmrEvents(api, cmd.windowMs ?? 5000);
105
+ const hmrTraceText = String(await api.viteHMRTrace("trace", 20));
106
+ const fallbackModules = extractModules(hmrTraceText);
107
+ const baseCorrelation = errorText === "no errors"
108
+ ? null
109
+ : correlateErrorWithHMR(errorText, [...events, ...fallbackHmrEvents], cmd.windowMs ?? 5000);
110
+ const data = formatErrorCorrelationReport(errorText, errorText === "no errors"
111
+ ? null
112
+ : upgradeErrorCorrelation(baseCorrelation, fallbackModules, cmd.windowMs ?? 5000));
114
113
  return { ok: true, data };
115
114
  }
116
115
  if (cmd.action === "correlate-renders") {
117
- const events = queue ? queue.window(cmd.windowMs ?? 5000) : [];
118
- const data = formatPropagationTraceReport(correlateRenderPropagation(events));
116
+ const events = await getSettledEventWindow(api, queue, cmd.windowMs ?? 5000);
117
+ const data = formatPropagationTraceReport(await buildPropagationTrace(api, events, cmd.windowMs ?? 5000));
119
118
  return { ok: true, data };
120
119
  }
121
120
  if (cmd.action === "diagnose-hmr") {
@@ -128,8 +127,8 @@ export function createRunner(api = browser) {
128
127
  return { ok: true, data };
129
128
  }
130
129
  if (cmd.action === "diagnose-propagation") {
131
- const events = queue ? queue.window(cmd.windowMs ?? 5000) : [];
132
- const data = formatPropagationDiagnosisReport(diagnosePropagation(correlateRenderPropagation(events)));
130
+ const events = await getSettledEventWindow(api, queue, cmd.windowMs ?? 5000);
131
+ const data = formatPropagationDiagnosisReport(diagnosePropagation(await buildPropagationTrace(api, events, cmd.windowMs ?? 5000)));
133
132
  return { ok: true, data };
134
133
  }
135
134
  if (cmd.action === "logs") {
@@ -143,6 +142,10 @@ export function createRunner(api = browser) {
143
142
  }
144
143
  if (cmd.action === "eval") {
145
144
  const data = await api.evaluate(cmd.script);
145
+ await settleCurrentPage(api, 120);
146
+ await flushCurrentPageEvents(api, queue);
147
+ await settleCurrentPage(api, 180);
148
+ await flushCurrentPageEvents(api, queue);
146
149
  return { ok: true, data };
147
150
  }
148
151
  if (cmd.action === "network") {
@@ -152,6 +155,133 @@ export function createRunner(api = browser) {
152
155
  return { ok: false, error: `unknown action: ${cmd.action}` };
153
156
  };
154
157
  }
158
+ async function buildPropagationTrace(api, events, windowMs) {
159
+ const hmrTraceText = String(await api.viteHMRTrace("trace", 20));
160
+ const fallbackModules = extractModules(hmrTraceText);
161
+ const trace = correlateRenderPropagation([...events, ...readTrackedHmrEvents(api, windowMs)]);
162
+ if (!trace)
163
+ return null;
164
+ let augmented = trace;
165
+ if (augmented.sourceModules.length === 0 && fallbackModules.length > 0) {
166
+ augmented = {
167
+ ...augmented,
168
+ sourceModules: fallbackModules,
169
+ };
170
+ }
171
+ if (augmented.changedKeys.length === 0 && augmented.storeUpdates.length > 0) {
172
+ const inferredChangedKeys = await inferLikelyChangedKeys(api, augmented.storeUpdates[0]);
173
+ if (inferredChangedKeys.length > 0) {
174
+ augmented = {
175
+ ...augmented,
176
+ changedKeys: inferredChangedKeys,
177
+ };
178
+ }
179
+ }
180
+ if (augmented.errorMessages.length > 0)
181
+ return augmented;
182
+ const currentError = String(await api.errors(false, false));
183
+ if (!currentError || currentError === "no errors")
184
+ return augmented;
185
+ return {
186
+ ...augmented,
187
+ errorMessages: [currentError, ...augmented.errorMessages],
188
+ };
189
+ }
190
+ function upgradeErrorCorrelation(correlation, fallbackModules, windowMs) {
191
+ if (fallbackModules.length === 0)
192
+ return correlation;
193
+ if (!correlation)
194
+ return synthesizeErrorCorrelation(fallbackModules, windowMs);
195
+ if (correlation.matchingModules.length > 0)
196
+ return correlation;
197
+ return synthesizeErrorCorrelation(fallbackModules, windowMs, correlation.relatedEvents);
198
+ }
199
+ function synthesizeErrorCorrelation(modules, windowMs, relatedEvents = []) {
200
+ if (modules.length === 0)
201
+ return null;
202
+ return {
203
+ summary: `HMR update observed within ${windowMs}ms of the current error`,
204
+ detail: `Matching modules: ${modules.join(", ")}\nRecent events considered: ${modules.length}`,
205
+ confidence: "high",
206
+ windowMs,
207
+ matchingModules: modules,
208
+ relatedEvents: relatedEvents.length > 0
209
+ ? relatedEvents
210
+ : modules.map((module) => ({
211
+ timestamp: Date.now(),
212
+ type: "hmr-update",
213
+ payload: {
214
+ path: module,
215
+ updates: [{ path: module }],
216
+ },
217
+ })),
218
+ };
219
+ }
220
+ async function inferLikelyChangedKeys(api, storeName) {
221
+ try {
222
+ const raw = await api.evaluate(`(() => {
223
+ const store = window.__PINIA__?._s?.get(${JSON.stringify(storeName)});
224
+ if (!store) return [];
225
+ return Object.keys(store).filter((key) => store[key] === undefined);
226
+ })()`);
227
+ const parsed = JSON.parse(String(raw));
228
+ return Array.isArray(parsed) ? parsed.filter((value) => typeof value === "string" && value.length > 0) : [];
229
+ }
230
+ catch {
231
+ return [];
232
+ }
233
+ }
234
+ function readTrackedHmrEvents(api, windowMs) {
235
+ const reader = api.getTrackedHmrEvents;
236
+ if (typeof reader !== "function")
237
+ return [];
238
+ const events = reader(windowMs);
239
+ return Array.isArray(events) ? events : [];
240
+ }
241
+ async function flushCurrentPageEvents(api, queue) {
242
+ if (!queue)
243
+ return;
244
+ try {
245
+ const currentPage = api.getCurrentPage();
246
+ if (currentPage) {
247
+ await api.flushBrowserEvents(currentPage, queue);
248
+ }
249
+ }
250
+ catch {
251
+ // Ignore flush errors (page might not be open yet)
252
+ }
253
+ }
254
+ async function getSettledEventWindow(api, queue, windowMs) {
255
+ if (!queue)
256
+ return [];
257
+ let events = queue.window(windowMs);
258
+ if (hasPropagationSignals(events))
259
+ return events;
260
+ for (const delayMs of [120, 300]) {
261
+ await settleCurrentPage(api, delayMs);
262
+ await flushCurrentPageEvents(api, queue);
263
+ events = queue.window(windowMs);
264
+ if (hasPropagationSignals(events))
265
+ return events;
266
+ }
267
+ return events;
268
+ }
269
+ function hasPropagationSignals(events) {
270
+ return events.some((event) => event.type === "render" || event.type === "store-update");
271
+ }
272
+ async function settleCurrentPage(api, delayMs) {
273
+ const currentPage = api.getCurrentPage();
274
+ if (!currentPage) {
275
+ await sleep(delayMs);
276
+ return;
277
+ }
278
+ try {
279
+ await currentPage.waitForTimeout(delayMs);
280
+ }
281
+ catch {
282
+ await sleep(delayMs);
283
+ }
284
+ }
155
285
  export async function dispatchLine(line, socket, run = createRunner(), onClose) {
156
286
  let cmd;
157
287
  try {
@@ -27,6 +27,8 @@ export declare function getRenderLabel(payload: RenderEventPayload): string;
27
27
  export declare function getStoreName(payload: StoreUpdatePayload): string | null;
28
28
  export declare function getChangedKeys(payload: StoreUpdatePayload): string[];
29
29
  export declare function getStoreHints(payload: RenderEventPayload): string[];
30
+ export declare function getRenderReason(payload: RenderEventPayload): string | null;
31
+ export declare function getRenderChangedKeys(payload: RenderEventPayload): string[];
30
32
  export declare function getNetworkUrl(payload: NetworkEventPayload): string | null;
31
33
  export declare function getErrorMessage(payload: ErrorEventPayload): string | null;
32
34
  export declare function uniqueStrings(values: string[]): string[];
@@ -59,6 +59,12 @@ export function getChangedKeys(payload) {
59
59
  export function getStoreHints(payload) {
60
60
  return payload.storeHints.filter((value) => value.length > 0);
61
61
  }
62
+ export function getRenderReason(payload) {
63
+ return typeof payload.reason === "string" && payload.reason.length > 0 ? payload.reason : null;
64
+ }
65
+ export function getRenderChangedKeys(payload) {
66
+ return payload.changedKeys.filter((value) => value.length > 0);
67
+ }
62
68
  export function getNetworkUrl(payload) {
63
69
  return payload.url.length > 0 ? payload.url : null;
64
70
  }
package/dist/trace.js CHANGED
@@ -1,4 +1,4 @@
1
- import { extractModulesFromHmrEvent, getChangedKeys, getErrorEvents, getErrorMessage, getHmrEvents, getNetworkEvents, getNetworkUrl, getRenderEvents, getRenderLabel, getStoreHints, getStoreName, getStoreUpdateEvents, sortEventsChronologically, uniqueStrings, } from "./event-analysis.js";
1
+ import { extractModulesFromHmrEvent, getChangedKeys, getErrorEvents, getErrorMessage, getHmrEvents, getNetworkEvents, getNetworkUrl, getRenderChangedKeys, getRenderEvents, getRenderLabel, getRenderReason, getStoreHints, getStoreName, getStoreUpdateEvents, sortEventsChronologically, uniqueStrings, } from "./event-analysis.js";
2
2
  export function correlateRenderPropagation(events) {
3
3
  const recent = sortEventsChronologically(events);
4
4
  const renderEvents = getRenderEvents(recent);
@@ -9,10 +9,25 @@ export function correlateRenderPropagation(events) {
9
9
  const networkEvents = getNetworkEvents(recent);
10
10
  const errorEvents = getErrorEvents(recent);
11
11
  const sourceModules = uniqueStrings(hmrEvents.flatMap(extractModulesFromHmrEvent));
12
- const storeUpdates = uniqueStrings(storeEvents.map((event) => getStoreName(event.payload)).filter((value) => value != null));
13
- const changedKeys = uniqueStrings(storeEvents.flatMap((event) => getChangedKeys(event.payload)));
14
- const renderComponents = uniqueStrings(renderEvents.map((event) => getRenderLabel(event.payload)).filter(Boolean));
12
+ const explicitStoreUpdates = uniqueStrings(storeEvents.map((event) => getStoreName(event.payload)).filter((value) => value != null));
15
13
  const storeHints = uniqueStrings(renderEvents.flatMap((event) => getStoreHints(event.payload)));
14
+ const inferredStoreUpdates = explicitStoreUpdates.length > 0
15
+ ? explicitStoreUpdates
16
+ : uniqueStrings(renderEvents
17
+ .filter((event) => getRenderReason(event.payload) === "store-update")
18
+ .flatMap((event) => getStoreHints(event.payload)));
19
+ const storeUpdates = inferredStoreUpdates.length > 0
20
+ ? inferredStoreUpdates
21
+ : storeHints.length === 1
22
+ ? storeHints
23
+ : [];
24
+ const changedKeys = uniqueStrings([
25
+ ...storeEvents.flatMap((event) => getChangedKeys(event.payload)),
26
+ ...renderEvents
27
+ .filter((event) => getRenderReason(event.payload) === "store-update")
28
+ .flatMap((event) => getRenderChangedKeys(event.payload)),
29
+ ]);
30
+ const renderComponents = uniqueStrings(renderEvents.map((event) => getRenderLabel(event.payload)).filter(Boolean));
16
31
  const networkUrls = uniqueStrings(networkEvents.map((event) => getNetworkUrl(event.payload)).filter((value) => value != null));
17
32
  const errorMessages = uniqueStrings(errorEvents.map((event) => getErrorMessage(event.payload)).filter((value) => value != null));
18
33
  const confidence = inferConfidence(sourceModules, storeUpdates, renderComponents, errorMessages);
package/package.json CHANGED
@@ -1,65 +1,63 @@
1
- {
2
- "name": "@presto1314w/vite-devtools-browser",
3
- "version": "0.3.1",
4
- "description": "Runtime diagnostics CLI for Vite apps with event-stream correlation, HMR diagnosis, framework inspection, and mapped errors",
5
- "license": "MIT",
6
- "keywords": [
7
- "vite",
8
- "devtools",
9
- "debugging",
10
- "runtime-diagnostics",
11
- "hmr",
12
- "module-graph",
13
- "sourcemap",
14
- "vue",
15
- "react",
16
- "svelte",
17
- "cli",
18
- "ai-agents"
19
- ],
20
- "repository": {
21
- "type": "git",
22
- "url": "git+https://github.com/MapleCity1314/vite-browser.git"
23
- },
24
- "homepage": "https://github.com/MapleCity1314/vite-browser#readme",
25
- "bugs": {
26
- "url": "https://github.com/MapleCity1314/vite-browser/issues"
27
- },
28
- "type": "module",
29
- "engines": {
30
- "node": ">=20"
31
- },
32
- "files": [
33
- "dist"
34
- ],
35
- "bin": {
36
- "vite-browser": "dist/cli.js"
37
- },
38
- "scripts": {
39
- "start": "node --import tsx src/cli.ts",
40
- "dev": "tsx src/cli.ts",
41
- "typecheck": "tsc --noEmit",
42
- "build": "tsc -p tsconfig.build.json",
43
- "prepack": "pnpm build",
44
- "test": "vitest run",
45
- "test:watch": "vitest",
46
- "test:coverage": "vitest run --coverage",
47
- "test:evals": "vitest run --dir test/evals",
48
- "test:evals:ci": "vitest run --dir test/evals --coverage --reporter=default",
49
- "test:evals:e2e": "pnpm build && vitest run --config vitest.e2e.config.ts"
50
- },
51
- "dependencies": {
52
- "playwright": "^1.50.0",
53
- "source-map-js": "^1.2.1"
54
- },
55
- "devDependencies": {
56
- "@types/node": "^22.0.0",
57
- "@vitest/coverage-v8": "^4.0.18",
58
- "@vue/devtools-kit": "^7.3.2",
59
- "@vue/devtools-api": "^7.3.2",
60
- "tsx": "^4.20.6",
61
- "typescript": "^5.7.0",
62
- "vitest": "^4.0.16"
63
- },
64
- "packageManager": "pnpm@10.29.2"
65
- }
1
+ {
2
+ "name": "@presto1314w/vite-devtools-browser",
3
+ "version": "0.3.3",
4
+ "description": "Runtime diagnostics CLI for Vite apps with event-stream correlation, HMR diagnosis, framework inspection, and mapped errors",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "vite",
8
+ "devtools",
9
+ "debugging",
10
+ "runtime-diagnostics",
11
+ "hmr",
12
+ "module-graph",
13
+ "sourcemap",
14
+ "vue",
15
+ "react",
16
+ "svelte",
17
+ "cli",
18
+ "ai-agents"
19
+ ],
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/MapleCity1314/vite-browser.git"
23
+ },
24
+ "homepage": "https://github.com/MapleCity1314/vite-browser#readme",
25
+ "bugs": {
26
+ "url": "https://github.com/MapleCity1314/vite-browser/issues"
27
+ },
28
+ "type": "module",
29
+ "engines": {
30
+ "node": ">=20"
31
+ },
32
+ "files": [
33
+ "dist"
34
+ ],
35
+ "bin": {
36
+ "vite-browser": "dist/cli.js"
37
+ },
38
+ "dependencies": {
39
+ "playwright": "^1.50.0",
40
+ "source-map-js": "^1.2.1"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^22.0.0",
44
+ "@vitest/coverage-v8": "^4.0.18",
45
+ "@vue/devtools-kit": "^7.3.2",
46
+ "@vue/devtools-api": "^7.3.2",
47
+ "tsx": "^4.20.6",
48
+ "typescript": "^5.7.0",
49
+ "vitest": "^4.0.16"
50
+ },
51
+ "scripts": {
52
+ "start": "node --import tsx src/cli.ts",
53
+ "dev": "tsx src/cli.ts",
54
+ "typecheck": "tsc --noEmit",
55
+ "build": "tsc -p tsconfig.build.json",
56
+ "test": "vitest run",
57
+ "test:watch": "vitest",
58
+ "test:coverage": "vitest run --coverage",
59
+ "test:evals": "vitest run --dir test/evals",
60
+ "test:evals:ci": "vitest run --dir test/evals --coverage --reporter=default",
61
+ "test:evals:e2e": "pnpm build && vitest run --config vitest.e2e.config.ts"
62
+ }
63
+ }