react-pref-guard 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1638 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ PerfProfiler: () => PerfProfiler,
24
+ PerfProvider: () => PerfProvider,
25
+ withPerfGuard: () => withPerfGuard
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/PerfProvider.tsx
30
+ var import_react2 = require("react");
31
+
32
+ // src/collector.ts
33
+ var buffer = /* @__PURE__ */ new Map();
34
+ function collectMetric(metric2) {
35
+ const {
36
+ component,
37
+ phase,
38
+ actualDuration,
39
+ boundaryType
40
+ } = metric2;
41
+ let entry = buffer.get(component);
42
+ if (!entry) {
43
+ entry = {
44
+ renders: 0,
45
+ totalTime: 0,
46
+ maxTime: 0,
47
+ boundaryType,
48
+ phaseCounts: { mount: 0, update: 0 }
49
+ };
50
+ buffer.set(component, entry);
51
+ }
52
+ entry.renders += 1;
53
+ entry.totalTime += actualDuration;
54
+ entry.maxTime = Math.max(entry.maxTime, actualDuration);
55
+ if (phase === "mount" || phase === "update") {
56
+ entry.phaseCounts[phase] += 1;
57
+ }
58
+ }
59
+ function flushMetrics() {
60
+ const now = performance.now();
61
+ const snapshot = [];
62
+ for (const [component, metric2] of buffer.entries()) {
63
+ snapshot.push({
64
+ component,
65
+ renders: metric2.renders,
66
+ avgTime: metric2.renders > 0 ? metric2.totalTime / metric2.renders : 0,
67
+ maxTime: metric2.maxTime,
68
+ boundaryType: metric2.boundaryType,
69
+ phaseCounts: {
70
+ mount: metric2.phaseCounts.mount,
71
+ update: metric2.phaseCounts.update
72
+ },
73
+ timestamp: now
74
+ });
75
+ }
76
+ buffer.clear();
77
+ return snapshot;
78
+ }
79
+
80
+ // src/issue-store/issueStore.ts
81
+ var store = /* @__PURE__ */ new Map();
82
+ var listeners = /* @__PURE__ */ new Set();
83
+ function upsertIssue(issue) {
84
+ store.set(issue.id, issue);
85
+ notify();
86
+ }
87
+ function resolveIssue(id) {
88
+ const row = store.get(id);
89
+ if (!row) return;
90
+ row.status = "RESOLVED";
91
+ notify();
92
+ setTimeout(() => {
93
+ store.delete(id);
94
+ notify();
95
+ }, 1e4);
96
+ }
97
+ function subscribe(listener) {
98
+ listeners.add(listener);
99
+ listener(Array.from(store.values()));
100
+ return () => listeners.delete(listener);
101
+ }
102
+ function notify() {
103
+ const snapshot = Array.from(store.values());
104
+ listeners.forEach((l) => l(snapshot));
105
+ }
106
+
107
+ // src/warnings.ts
108
+ var activeIssues = /* @__PURE__ */ new Map();
109
+ var lastLogTime = /* @__PURE__ */ new Map();
110
+ var LOG_COOLDOWN = 3e4;
111
+ var RESOLVE_AFTER_MISSES = 3;
112
+ function fingerprint(component, issue) {
113
+ return `${component}:${issue.ruleId}:${issue.severity}`;
114
+ }
115
+ function shouldLog(fp) {
116
+ const last = lastLogTime.get(fp) || 0;
117
+ if (Date.now() - last < LOG_COOLDOWN) return false;
118
+ lastLogTime.set(fp, Date.now());
119
+ return true;
120
+ }
121
+ function severityEmoji(sev) {
122
+ switch (sev) {
123
+ case "CRITICAL":
124
+ return "\u{1F4A5}";
125
+ case "HIGH":
126
+ return "\u{1F534}";
127
+ case "MEDIUM":
128
+ return "\u{1F7E1}";
129
+ case "LOW":
130
+ return "\u{1F535}";
131
+ default:
132
+ return "\u2139\uFE0F";
133
+ }
134
+ }
135
+ function severityColor(sev) {
136
+ switch (sev) {
137
+ case "CRITICAL":
138
+ return "#dc2626";
139
+ case "HIGH":
140
+ return "#ef4444";
141
+ case "MEDIUM":
142
+ return "#f59e0b";
143
+ case "LOW":
144
+ return "#3b82f6";
145
+ default:
146
+ return "#6b7280";
147
+ }
148
+ }
149
+ function showWarning(result) {
150
+ const seenThisBatch = /* @__PURE__ */ new Set();
151
+ for (const issue of result.issues) {
152
+ const fp = fingerprint(result.component, issue);
153
+ seenThisBatch.add(fp);
154
+ const state = activeIssues.get(fp);
155
+ if (!state) {
156
+ activeIssues.set(fp, { issue, missingCount: 0 });
157
+ upsertIssue({
158
+ id: fp,
159
+ component: result.component,
160
+ ruleId: issue.ruleId,
161
+ severity: issue.severity,
162
+ confidence: issue.confidence,
163
+ boundaryType: result.boundaryType,
164
+ status: "NEW",
165
+ reason: issue.reason,
166
+ lastSeen: Date.now()
167
+ });
168
+ if (shouldLog(fp)) {
169
+ logNewIssue(result, issue);
170
+ }
171
+ } else {
172
+ state.missingCount = 0;
173
+ upsertIssue({
174
+ id: fp,
175
+ component: result.component,
176
+ ruleId: issue.ruleId,
177
+ severity: issue.severity,
178
+ // CRITICAL preserved
179
+ confidence: issue.confidence,
180
+ boundaryType: result.boundaryType,
181
+ status: "ACTIVE",
182
+ reason: issue.reason,
183
+ lastSeen: Date.now()
184
+ });
185
+ }
186
+ }
187
+ for (const [fp, state] of activeIssues.entries()) {
188
+ if (!seenThisBatch.has(fp)) {
189
+ state.missingCount++;
190
+ if (state.missingCount >= RESOLVE_AFTER_MISSES) {
191
+ activeIssues.delete(fp);
192
+ resolveIssue(fp);
193
+ logResolvedIssue(result.component, state.issue);
194
+ }
195
+ }
196
+ }
197
+ }
198
+ function showCriticalAlert(result) {
199
+ for (const issue of result.issues) {
200
+ if (issue.severity !== "CRITICAL") continue;
201
+ const fp = fingerprint(result.component, issue);
202
+ if (!shouldLog(fp)) continue;
203
+ showCriticalOverlay(result.component, issue);
204
+ }
205
+ }
206
+ function logNewIssue(result, issue) {
207
+ console.groupCollapsed(
208
+ `%c\u26A1 PerfGuard \xB7 ${result.component}`,
209
+ `color:${severityColor(issue.severity)};font-weight:bold`
210
+ );
211
+ console.info(
212
+ `%c${severityEmoji(issue.severity)} ${issue.ruleId} (${issue.severity})`,
213
+ `color:${severityColor(issue.severity)}`
214
+ );
215
+ console.info("Confidence:", `${Math.round(issue.confidence * 100)}%`);
216
+ console.info("Reason:", issue.reason);
217
+ console.info("Boundary:", result.boundaryType);
218
+ console.groupCollapsed("\u{1F4CA} Metrics");
219
+ console.table(result.metrics);
220
+ console.groupEnd();
221
+ console.groupEnd();
222
+ }
223
+ function logResolvedIssue(component, issue) {
224
+ console.info(
225
+ `%c\u2705 PerfGuard \xB7 RESOLVED \xB7 ${component} \xB7 ${issue.ruleId}`,
226
+ "color:#16a34a;font-weight:bold"
227
+ );
228
+ }
229
+ function showCriticalOverlay(component, issue) {
230
+ if (typeof document === "undefined") return;
231
+ const alert = document.createElement("div");
232
+ alert.style.cssText = `
233
+ position: fixed;
234
+ bottom: 20px;
235
+ right: 20px;
236
+ background: #dc2626;
237
+ color: white;
238
+ padding: 14px 18px;
239
+ border-radius: 8px;
240
+ box-shadow: 0 10px 20px rgba(0,0,0,0.35);
241
+ z-index: 10000;
242
+ font-family: monospace;
243
+ font-size: 13px;
244
+ max-width: 360px;
245
+ `;
246
+ alert.innerHTML = `
247
+ <strong>\u{1F4A5} PerfGuard \u2013 CRITICAL</strong><br/>
248
+ Component: ${component}<br/>
249
+ Rule: ${issue.ruleId}<br/>
250
+ Confidence: ${Math.round(issue.confidence * 100)}%
251
+ `;
252
+ document.body.appendChild(alert);
253
+ setTimeout(() => alert.remove(), 8e3);
254
+ }
255
+
256
+ // src/worker/createWorker.ts
257
+ function createAnalyzerWorker() {
258
+ const code = `
259
+ /* ===============================
260
+ PerfGuard \u2013 Enhanced Rule DSL Worker
261
+ =============================== */
262
+
263
+ const history = new Map();
264
+ const MAX_HISTORY = 10; // Increased for trend detection
265
+
266
+ const DOMINANT_RULES = new Set([
267
+ "BLOCKING_RENDER",
268
+ "SEVERE_PERF_REGRESSION",
269
+ "SUSPICIOUS_RENDER_LOOP",
270
+ "RENDER_TIME_CREEP",
271
+ ]);
272
+
273
+ let RULES = [];
274
+
275
+ /* -------------------------------
276
+ Utilities
277
+ -------------------------------- */
278
+
279
+ function hasDominantIssue(issues) {
280
+ return issues.some(issue => DOMINANT_RULES.has(issue.ruleId));
281
+ }
282
+
283
+ function record(snapshot) {
284
+ const list = history.get(snapshot.component) || [];
285
+ list.push(snapshot);
286
+ if (list.length > MAX_HISTORY) list.shift();
287
+ history.set(snapshot.component, list);
288
+ }
289
+
290
+ function calculateConfidenceWithCurrent(predicateFn, historyList, snapshot) {
291
+ if (historyList.length === 0) return 1;
292
+
293
+ const matches = historyList.filter(predicateFn).length;
294
+ const currentMatch = predicateFn(snapshot) ? 1 : 0;
295
+
296
+ return (matches + currentMatch) / (historyList.length + 1);
297
+ }
298
+
299
+ function downgradeSeverity(severity, confidence, boundaryType) {
300
+ // CRITICAL is NEVER downgraded
301
+ if (severity === "CRITICAL") return "CRITICAL";
302
+
303
+ // INLINE components downgrade by ONE level only
304
+ if (boundaryType === "INLINE") {
305
+ if (severity === "HIGH") return "MEDIUM";
306
+ if (severity === "MEDIUM") return "LOW";
307
+ return "INFO";
308
+ }
309
+
310
+ // \u{1F4C9} Confidence-based softening
311
+ if (confidence < 0.7) return "LOW";
312
+ if (confidence < 0.85) return "MEDIUM";
313
+
314
+ return severity;
315
+ }
316
+
317
+ function buildPredicate({ field, operator, value }) {
318
+ switch (operator) {
319
+ case ">": return (s) => s[field] > value;
320
+ case "<": return (s) => s[field] < value;
321
+ case ">=": return (s) => s[field] >= value;
322
+ case "<=": return (s) => s[field] <= value;
323
+ case "===": return (s) => s[field] === value;
324
+ default: return () => false;
325
+ }
326
+ }
327
+
328
+ function interpolateMessage(template, values) {
329
+ let result = template;
330
+ for (const [key, value] of Object.entries(values)) {
331
+ result = result.replace(
332
+ new RegExp(\`{\${key}}\`, 'g'),
333
+ typeof value === 'number' ? Math.round(value).toString() : value
334
+ );
335
+ }
336
+ return result;
337
+ }
338
+
339
+ /* -------------------------------
340
+ Trend Detection
341
+ -------------------------------- */
342
+
343
+ function detectTrend(historyList, field) {
344
+ if (historyList.length < 5) return { direction: 'stable', change: 0 };
345
+
346
+ // Use linear regression to detect trend
347
+ const values = historyList.map(s => s[field]);
348
+ const n = values.length;
349
+ const indices = Array.from({ length: n }, (_, i) => i);
350
+
351
+ const sumX = indices.reduce((a, b) => a + b, 0);
352
+ const sumY = values.reduce((a, b) => a + b, 0);
353
+ const sumXY = indices.reduce((sum, x, i) => sum + x * values[i], 0);
354
+ const sumX2 = indices.reduce((sum, x) => sum + x * x, 0);
355
+
356
+ const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
357
+ const avgValue = sumY / n;
358
+
359
+ // Calculate percentage change
360
+ const percentChange = (slope * n / avgValue) * 100;
361
+
362
+ return {
363
+ direction: slope > 0 ? 'increasing' : slope < 0 ? 'decreasing' : 'stable',
364
+ change: Math.abs(percentChange)
365
+ };
366
+ }
367
+
368
+ /* -------------------------------
369
+ Rule Evaluation Engine
370
+ -------------------------------- */
371
+
372
+ function evaluate(snapshot) {
373
+ const issues = [];
374
+ const historyList = history.get(snapshot.component) || [];
375
+ const previous = historyList[historyList.length - 1];
376
+
377
+ /* ===============================
378
+ PASS 1: DOMINANT RULES ONLY
379
+ =============================== */
380
+ for (const rule of RULES) {
381
+ if (!DOMINANT_RULES.has(rule.id)) continue;
382
+
383
+ /* ----- Regression Rule ----- */
384
+ if (rule.regression && previous) {
385
+ const field = rule.regression.field;
386
+ const multiplier = rule.regression.multiplier;
387
+
388
+ if (snapshot[field] > previous[field] * multiplier) {
389
+ issues.push({
390
+ ruleId: rule.id,
391
+ confidence: 1,
392
+ severity: downgradeSeverity(
393
+ rule.baseSeverity,
394
+ 1,
395
+ snapshot.boundaryType || "HOC"
396
+ ),
397
+ reason: interpolateMessage(rule.messageTemplate, {
398
+ prevValue: previous[field].toFixed(1),
399
+ currValue: snapshot[field].toFixed(1),
400
+ }),
401
+ });
402
+ }
403
+ continue;
404
+ }
405
+
406
+ /* ----- Trend Detection Rule ----- */
407
+ if (rule.trend && historyList.length >= 5) {
408
+ const trend = detectTrend(historyList, rule.trend.field);
409
+
410
+ if (
411
+ trend.direction === rule.trend.direction &&
412
+ trend.change >= rule.trend.threshold
413
+ ) {
414
+ const confidence = Math.min(trend.change / 100, 1.0);
415
+
416
+ issues.push({
417
+ ruleId: rule.id,
418
+ confidence,
419
+ severity: downgradeSeverity(
420
+ rule.baseSeverity,
421
+ confidence,
422
+ snapshot.boundaryType || "HOC"
423
+ ),
424
+ reason: interpolateMessage(rule.messageTemplate, {
425
+ change: trend.change.toFixed(1),
426
+ }),
427
+ });
428
+ }
429
+ continue;
430
+ }
431
+
432
+ /* ----- Predicate Rule ----- */
433
+ if (rule.predicate) {
434
+ const predicateFn = buildPredicate(rule.predicate);
435
+ const confidence = calculateConfidenceWithCurrent(
436
+ predicateFn,
437
+ historyList,
438
+ snapshot
439
+ );
440
+ const threshold = rule.confidenceThreshold || 0.6;
441
+
442
+ if (predicateFn(snapshot) && confidence >= threshold) {
443
+ issues.push({
444
+ ruleId: rule.id,
445
+ confidence,
446
+ severity: downgradeSeverity(
447
+ rule.baseSeverity,
448
+ confidence,
449
+ snapshot.boundaryType || "HOC"
450
+ ),
451
+ reason: interpolateMessage(rule.messageTemplate, {
452
+ confidence: (confidence * 100).toFixed(0),
453
+ }),
454
+ });
455
+ }
456
+ }
457
+ }
458
+
459
+ /* ===============================
460
+ If dominant issue exists \u2192 stop
461
+ =============================== */
462
+ if (issues.length) {
463
+ // Allow DEV_HINT rules only
464
+ for (const rule of RULES) {
465
+ if (!rule.id.startsWith("DEV_HINT")) continue;
466
+ if (!rule.predicate) continue;
467
+
468
+ const predicateFn = buildPredicate(rule.predicate);
469
+ const confidence = calculateConfidenceWithCurrent(
470
+ predicateFn,
471
+ historyList,
472
+ snapshot
473
+ );
474
+ const threshold = rule.confidenceThreshold || 0.6;
475
+
476
+ if (predicateFn(snapshot) && confidence >= threshold) {
477
+ issues.push({
478
+ ruleId: rule.id,
479
+ confidence,
480
+ severity: "INFO",
481
+ reason: interpolateMessage(rule.messageTemplate, {
482
+ confidence: (confidence * 100).toFixed(0),
483
+ }),
484
+ });
485
+ }
486
+ }
487
+
488
+ record(snapshot);
489
+ return issues;
490
+ }
491
+
492
+ /* ===============================
493
+ PASS 2: NORMAL RULES
494
+ =============================== */
495
+ for (const rule of RULES) {
496
+
497
+ /* ----- Regression Rule ----- */
498
+ if (rule.regression && previous) {
499
+ const field = rule.regression.field;
500
+ const multiplier = rule.regression.multiplier;
501
+
502
+ if (snapshot[field] > previous[field] * multiplier) {
503
+ issues.push({
504
+ ruleId: rule.id,
505
+ confidence: 1,
506
+ severity: downgradeSeverity(
507
+ rule.baseSeverity,
508
+ 1,
509
+ snapshot.boundaryType || "HOC"
510
+ ),
511
+ reason: interpolateMessage(rule.messageTemplate, {
512
+ prevValue: previous[field].toFixed(1),
513
+ currValue: snapshot[field].toFixed(1),
514
+ }),
515
+ });
516
+ }
517
+ continue;
518
+ }
519
+
520
+ /* ----- Trend Detection Rule ----- */
521
+ if (rule.trend && historyList.length >= 5) {
522
+ const trend = detectTrend(historyList, rule.trend.field);
523
+
524
+ if (
525
+ trend.direction === rule.trend.direction &&
526
+ trend.change >= rule.trend.threshold
527
+ ) {
528
+ const confidence = Math.min(trend.change / 100, 1.0);
529
+
530
+ issues.push({
531
+ ruleId: rule.id,
532
+ confidence,
533
+ severity: downgradeSeverity(
534
+ rule.baseSeverity,
535
+ confidence,
536
+ snapshot.boundaryType || "HOC"
537
+ ),
538
+ reason: interpolateMessage(rule.messageTemplate, {
539
+ change: trend.change.toFixed(1),
540
+ }),
541
+ });
542
+ }
543
+ continue;
544
+ }
545
+
546
+ /* ----- Predicate Rule ----- */
547
+ if (rule.predicate) {
548
+ const predicateFn = buildPredicate(rule.predicate);
549
+ const confidence = calculateConfidenceWithCurrent(
550
+ predicateFn,
551
+ historyList,
552
+ snapshot
553
+ );
554
+ const threshold = rule.confidenceThreshold || 0.6;
555
+
556
+ if (predicateFn(snapshot) && confidence >= threshold) {
557
+ issues.push({
558
+ ruleId: rule.id,
559
+ confidence,
560
+ severity: downgradeSeverity(
561
+ rule.baseSeverity,
562
+ confidence,
563
+ snapshot.boundaryType || "HOC"
564
+ ),
565
+ reason: interpolateMessage(rule.messageTemplate, {
566
+ confidence: (confidence * 100).toFixed(0),
567
+ }),
568
+ });
569
+ }
570
+ }
571
+ }
572
+
573
+ record(snapshot);
574
+ return issues;
575
+ }
576
+
577
+
578
+ /* -------------------------------
579
+ Worker Message Handler
580
+ -------------------------------- */
581
+
582
+ self.onmessage = (e) => {
583
+ const { type, payload } = e.data;
584
+
585
+ /* Init rules once */
586
+ if (type === "INIT_RULES") {
587
+ RULES = payload;
588
+ // console.log('[PerfGuard Worker] Rules loaded:', RULES.length);
589
+ self.postMessage({ type: "INIT_SUCCESS", count: RULES.length });
590
+ return;
591
+ }
592
+
593
+ /* Evaluate snapshots */
594
+ if (type === "EVALUATE") {
595
+ const results = [];
596
+ let hasCritical = false;
597
+
598
+ for (const snapshot of payload) {
599
+ const issues = evaluate(snapshot);
600
+
601
+ if (issues.length) {
602
+ const componentResult = {
603
+ component: snapshot.component,
604
+ boundaryType: snapshot.boundaryType || "HOC",
605
+ metrics: {
606
+ renders: snapshot.renders,
607
+ avgTime: snapshot.avgTime,
608
+ maxTime: snapshot.maxTime,
609
+ },
610
+ issues,
611
+ };
612
+
613
+ // Check for critical issues
614
+ if (issues.some(i => i.severity === 'CRITICAL')) {
615
+ hasCritical = true;
616
+ componentResult.hasCritical = true;
617
+ }
618
+
619
+ results.push(componentResult);
620
+ }
621
+ }
622
+
623
+ // console.log('[PerfGuard Worker] Evaluation complete:', results.length, 'issues');
624
+ self.postMessage({
625
+ type: "RESULTS",
626
+ data: results,
627
+ hasCritical,
628
+ timestamp: Date.now()
629
+ });
630
+ }
631
+
632
+ /* Reset history */
633
+ if (type === "RESET") {
634
+ history.clear();
635
+ self.postMessage({ type: "RESET_SUCCESS" });
636
+ }
637
+
638
+ /* Get stats */
639
+ if (type === "GET_STATS") {
640
+ const stats = {
641
+ componentsTracked: history.size,
642
+ totalSnapshots: Array.from(history.values()).reduce((sum, list) => sum + list.length, 0),
643
+ rulesLoaded: RULES.length,
644
+ };
645
+ self.postMessage({ type: "STATS", data: stats });
646
+ }
647
+ };
648
+ `;
649
+ return new Worker(
650
+ URL.createObjectURL(new Blob([code], { type: "application/javascript" }))
651
+ );
652
+ }
653
+
654
+ // src/env.ts
655
+ var isDev = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
656
+
657
+ // src/pref-engine/rules.ts
658
+ var PERF_RULES = [
659
+ // ========================================
660
+ // CRITICAL PERFORMANCE RULES
661
+ // ========================================
662
+ {
663
+ id: "BLOCKING_RENDER",
664
+ category: "PERFORMANCE",
665
+ baseSeverity: "CRITICAL",
666
+ predicate: {
667
+ field: "avgTime",
668
+ operator: ">",
669
+ value: 100
670
+ },
671
+ confidenceThreshold: 0.4,
672
+ messageTemplate: "CRITICAL: Component blocks UI thread for >100ms in {confidence}% of renders",
673
+ messageFields: ["confidence"],
674
+ docUrl: "https://web.dev/rail/"
675
+ },
676
+ {
677
+ id: "SLOW_RENDER",
678
+ category: "PERFORMANCE",
679
+ baseSeverity: "HIGH",
680
+ predicate: {
681
+ field: "avgTime",
682
+ operator: ">",
683
+ value: 16
684
+ },
685
+ confidenceThreshold: 0.6,
686
+ messageTemplate: "Average render time exceeded 16ms in {confidence}% of recent batches",
687
+ messageFields: ["confidence"]
688
+ },
689
+ {
690
+ id: "VERY_SLOW_RENDER",
691
+ category: "PERFORMANCE",
692
+ baseSeverity: "HIGH",
693
+ predicate: {
694
+ field: "avgTime",
695
+ operator: ">",
696
+ value: 50
697
+ },
698
+ confidenceThreshold: 0.5,
699
+ messageTemplate: "Critical: render time exceeded 50ms in {confidence}% of batches",
700
+ messageFields: ["confidence"]
701
+ },
702
+ {
703
+ id: "INCONSISTENT_PERFORMANCE",
704
+ category: "STABILITY",
705
+ baseSeverity: "MEDIUM",
706
+ predicate: {
707
+ field: "maxTime",
708
+ operator: ">",
709
+ value: 50
710
+ },
711
+ confidenceThreshold: 0.3,
712
+ messageTemplate: "Unstable performance: max render time spikes to >50ms",
713
+ messageFields: ["confidence"]
714
+ },
715
+ // ========================================
716
+ // RENDER OPTIMIZATION RULES
717
+ // ========================================
718
+ {
719
+ id: "EXCESSIVE_RENDERS",
720
+ category: "PERFORMANCE",
721
+ baseSeverity: "MEDIUM",
722
+ predicate: {
723
+ field: "renders",
724
+ operator: ">",
725
+ value: 20
726
+ },
727
+ confidenceThreshold: 0.6,
728
+ messageTemplate: "Component rendered excessively in {confidence}% of recent batches",
729
+ messageFields: ["confidence"]
730
+ },
731
+ {
732
+ id: "RENDER_THRASHING",
733
+ category: "PERFORMANCE",
734
+ baseSeverity: "HIGH",
735
+ predicate: {
736
+ field: "renders",
737
+ operator: ">",
738
+ value: 50
739
+ },
740
+ confidenceThreshold: 0.4,
741
+ messageTemplate: "Severe render thrashing detected: >50 renders in {confidence}% of batches",
742
+ messageFields: ["confidence"]
743
+ },
744
+ {
745
+ id: "SUSPICIOUS_RENDER_LOOP",
746
+ category: "RELIABILITY",
747
+ baseSeverity: "CRITICAL",
748
+ predicate: {
749
+ field: "renders",
750
+ operator: ">",
751
+ value: 100
752
+ },
753
+ confidenceThreshold: 0.2,
754
+ messageTemplate: "Potential infinite render loop: >100 renders detected",
755
+ messageFields: ["confidence"]
756
+ },
757
+ // ========================================
758
+ // REGRESSION DETECTION
759
+ // ========================================
760
+ {
761
+ id: "PERF_REGRESSION",
762
+ category: "PERFORMANCE",
763
+ baseSeverity: "HIGH",
764
+ regression: {
765
+ field: "avgTime",
766
+ multiplier: 1.3
767
+ },
768
+ messageTemplate: "Avg render time increased from {prevValue}ms to {currValue}ms",
769
+ messageFields: ["prevValue", "currValue"]
770
+ },
771
+ {
772
+ id: "SEVERE_PERF_REGRESSION",
773
+ category: "PERFORMANCE",
774
+ baseSeverity: "CRITICAL",
775
+ regression: {
776
+ field: "avgTime",
777
+ multiplier: 2
778
+ },
779
+ messageTemplate: "SEVERE: Performance degraded by 2x from {prevValue}ms to {currValue}ms",
780
+ messageFields: ["prevValue", "currValue"]
781
+ },
782
+ {
783
+ id: "RENDER_COUNT_SPIKE",
784
+ category: "STABILITY",
785
+ baseSeverity: "MEDIUM",
786
+ regression: {
787
+ field: "renders",
788
+ multiplier: 1.5
789
+ },
790
+ messageTemplate: "Render count increased from {prevValue} to {currValue}",
791
+ messageFields: ["prevValue", "currValue"]
792
+ },
793
+ {
794
+ id: "MAX_TIME_REGRESSION",
795
+ category: "STABILITY",
796
+ baseSeverity: "HIGH",
797
+ regression: {
798
+ field: "maxTime",
799
+ multiplier: 1.5
800
+ },
801
+ messageTemplate: "Peak render time degraded from {prevValue}ms to {currValue}ms",
802
+ messageFields: ["prevValue", "currValue"]
803
+ },
804
+ // ========================================
805
+ // USER EXPERIENCE RULES
806
+ // ========================================
807
+ {
808
+ id: "JANKY_ANIMATION",
809
+ category: "UX",
810
+ baseSeverity: "MEDIUM",
811
+ predicate: {
812
+ field: "avgTime",
813
+ operator: ">",
814
+ value: 16.67
815
+ // 60fps threshold
816
+ },
817
+ confidenceThreshold: 0.7,
818
+ messageTemplate: "Janky animations: unable to maintain 60fps in {confidence}% of renders",
819
+ messageFields: ["confidence"]
820
+ },
821
+ {
822
+ id: "POOR_INTERACTION_RESPONSE",
823
+ category: "UX",
824
+ baseSeverity: "HIGH",
825
+ predicate: {
826
+ field: "avgTime",
827
+ operator: ">",
828
+ value: 50
829
+ },
830
+ confidenceThreshold: 0.5,
831
+ messageTemplate: "Poor interaction response: >50ms delay in {confidence}% of renders",
832
+ messageFields: ["confidence"]
833
+ },
834
+ // ========================================
835
+ // MEMORY & LEAK DETECTION
836
+ // ========================================
837
+ {
838
+ id: "RENDER_TIME_CREEP",
839
+ category: "MEMORY",
840
+ baseSeverity: "MEDIUM",
841
+ trend: {
842
+ field: "avgTime",
843
+ direction: "increasing",
844
+ threshold: 20
845
+ // 20% increase over time
846
+ },
847
+ messageTemplate: "Gradual performance degradation: render time increasing over time",
848
+ confidenceThreshold: 0.6
849
+ },
850
+ {
851
+ id: "RENDER_COUNT_CREEP",
852
+ category: "MEMORY",
853
+ baseSeverity: "MEDIUM",
854
+ trend: {
855
+ field: "renders",
856
+ direction: "increasing",
857
+ threshold: 30
858
+ },
859
+ messageTemplate: "Render count steadily increasing - possible state management issue",
860
+ confidenceThreshold: 0.6
861
+ },
862
+ // ========================================
863
+ // STABILITY & RELIABILITY
864
+ // ========================================
865
+ {
866
+ id: "ERRATIC_PERFORMANCE",
867
+ category: "STABILITY",
868
+ baseSeverity: "MEDIUM",
869
+ predicate: {
870
+ field: "maxTime",
871
+ operator: ">",
872
+ value: 100
873
+ },
874
+ confidenceThreshold: 0.3,
875
+ messageTemplate: "Erratic performance spikes detected (>100ms)",
876
+ messageFields: ["confidence"]
877
+ },
878
+ {
879
+ id: "FIRST_RENDER_SLOW",
880
+ category: "UX",
881
+ baseSeverity: "MEDIUM",
882
+ predicate: {
883
+ field: "maxTime",
884
+ operator: ">",
885
+ value: 200
886
+ },
887
+ confidenceThreshold: 0.8,
888
+ messageTemplate: "Slow initial mount: first render >200ms",
889
+ messageFields: ["confidence"]
890
+ },
891
+ // ========================================
892
+ // PRODUCTION READINESS CHECKS
893
+ // ========================================
894
+ {
895
+ id: "PROD_READY_PERF",
896
+ category: "PERFORMANCE",
897
+ baseSeverity: "INFO",
898
+ predicate: {
899
+ field: "avgTime",
900
+ operator: "<",
901
+ value: 10
902
+ },
903
+ confidenceThreshold: 0.9,
904
+ messageTemplate: "\u2705 Component meets production performance standards (<10ms avg)",
905
+ messageFields: ["confidence"]
906
+ },
907
+ // ========================================
908
+ // HELPFUL HINTS (DEVELOPMENT)
909
+ // ========================================
910
+ {
911
+ id: "DEV_HINT_MEMOIZATION",
912
+ category: "PERFORMANCE",
913
+ baseSeverity: "INFO",
914
+ predicate: {
915
+ field: "renders",
916
+ operator: ">",
917
+ value: 15
918
+ },
919
+ confidenceThreshold: 0.5,
920
+ messageTemplate: "\u{1F4A1} Consider React.memo or useMemo for this component",
921
+ messageFields: ["confidence"]
922
+ },
923
+ {
924
+ id: "DEV_HINT_OPTIMIZATION",
925
+ category: "PERFORMANCE",
926
+ baseSeverity: "INFO",
927
+ predicate: {
928
+ field: "avgTime",
929
+ operator: ">",
930
+ value: 20
931
+ },
932
+ confidenceThreshold: 0.5,
933
+ messageTemplate: "\u{1F4A1} Component could benefit from optimization (>20ms avg)",
934
+ messageFields: ["confidence"]
935
+ }
936
+ ];
937
+ function getRulesConfig() {
938
+ return PERF_RULES;
939
+ }
940
+ var RULE_GROUPS = {
941
+ CRITICAL_ONLY: PERF_RULES.filter((r) => r.baseSeverity === "CRITICAL"),
942
+ PERFORMANCE: PERF_RULES.filter((r) => r.category === "PERFORMANCE"),
943
+ UX: PERF_RULES.filter((r) => r.category === "UX"),
944
+ STABILITY: PERF_RULES.filter((r) => r.category === "STABILITY"),
945
+ MEMORY: PERF_RULES.filter((r) => r.category === "MEMORY"),
946
+ HINTS: PERF_RULES.filter((r) => r.id.startsWith("DEV_HINT") || r.id.startsWith("PROD_READY"))
947
+ };
948
+
949
+ // src/PrevGuardPanel.tsx
950
+ var import_react = require("react");
951
+ var import_jsx_runtime = require("react/jsx-runtime");
952
+ function PerfGuardPanel() {
953
+ const [issues, setIssues] = (0, import_react.useState)([]);
954
+ const [open, setOpen] = (0, import_react.useState)(false);
955
+ const [selectedIssue, setSelectedIssue] = (0, import_react.useState)(null);
956
+ (0, import_react.useEffect)(() => {
957
+ subscribe(setIssues);
958
+ }, []);
959
+ if (!issues.length) {
960
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: panel, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: header, children: [
961
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: headerTitle, children: "PerfGuard" }),
962
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: headerSubtitle, children: "No issues detected" })
963
+ ] }) });
964
+ }
965
+ const criticalCount = issues.filter(
966
+ (i) => i.severity === "CRITICAL" && i.status === "ACTIVE"
967
+ ).length;
968
+ const highCount = issues.filter(
969
+ (i) => i.severity === "HIGH" && i.status === "ACTIVE"
970
+ ).length;
971
+ const activeCount = issues.filter(
972
+ (i) => i.status === "ACTIVE" || i.status === "NEW"
973
+ ).length;
974
+ const getSeverityClass = (severity) => {
975
+ switch (severity) {
976
+ case "CRITICAL":
977
+ return "severity-critical";
978
+ case "HIGH":
979
+ return "severity-high";
980
+ case "MEDIUM":
981
+ return "severity-medium";
982
+ default:
983
+ return "severity-low";
984
+ }
985
+ };
986
+ const sortedIssues = [...issues].sort((a, b) => {
987
+ if (a.status === b.status) return 0;
988
+ return a.status === "ACTIVE" ? -1 : 1;
989
+ });
990
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
991
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: styles }),
992
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: panel, children: [
993
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
994
+ "div",
995
+ {
996
+ style: header,
997
+ onClick: () => setOpen((o) => !o),
998
+ className: "perfguard-header",
999
+ children: [
1000
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: headerLeft, children: [
1001
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: iconWrapper, children: [
1002
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: icon, children: "\u26A1" }),
1003
+ criticalCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: criticalPing })
1004
+ ] }),
1005
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1006
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: headerTitle, children: [
1007
+ "PerfGuard",
1008
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: betaBadge, children: "BETA" })
1009
+ ] }),
1010
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: headerSubtitle, children: [
1011
+ activeCount,
1012
+ " active issue",
1013
+ activeCount !== 1 ? "s" : ""
1014
+ ] })
1015
+ ] })
1016
+ ] }),
1017
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: headerRight, children: [
1018
+ criticalCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: criticalBadge, className: "critical-badge", children: [
1019
+ criticalCount,
1020
+ " Critical"
1021
+ ] }),
1022
+ highCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: highBadge, children: [
1023
+ highCount,
1024
+ " High"
1025
+ ] }),
1026
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1027
+ "svg",
1028
+ {
1029
+ style: {
1030
+ ...chevron,
1031
+ transform: open ? "rotate(180deg)" : "rotate(0deg)"
1032
+ },
1033
+ fill: "none",
1034
+ viewBox: "0 0 24 24",
1035
+ stroke: "currentColor",
1036
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1037
+ "path",
1038
+ {
1039
+ strokeLinecap: "round",
1040
+ strokeLinejoin: "round",
1041
+ strokeWidth: 2,
1042
+ d: "M19 9l-7 7-7-7"
1043
+ }
1044
+ )
1045
+ }
1046
+ )
1047
+ ] })
1048
+ ]
1049
+ }
1050
+ ),
1051
+ open && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: issuesContainer, className: "issues-container", children: [
1052
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: issuesList, children: sortedIssues.map((issue, idx) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1053
+ "div",
1054
+ {
1055
+ onClick: () => setSelectedIssue(
1056
+ selectedIssue === issue.id ? null : issue.id
1057
+ ),
1058
+ style: issueCard(idx),
1059
+ className: `issue-card ${getSeverityClass(issue.severity)}`,
1060
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: issueContent, children: [
1061
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: issueMain, children: [
1062
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: issueHeader, children: [
1063
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1064
+ "span",
1065
+ {
1066
+ style: statusDot(issue.status),
1067
+ className: issue.status === "ACTIVE" ? "status-dot-pulse" : ""
1068
+ }
1069
+ ),
1070
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: componentName, children: issue.component }),
1071
+ issue.status === "RESOLVED" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: statusBadgeResolved, children: "\u2713 Resolved" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1072
+ "span",
1073
+ {
1074
+ style: statusBadgeOpen,
1075
+ className: "status-badge-open",
1076
+ children: "\u25CF Open"
1077
+ }
1078
+ )
1079
+ ] }),
1080
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: ruleId, children: issue.ruleId.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ") }),
1081
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: issueMetrics, children: [
1082
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: metric, children: [
1083
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: metricLabel, children: "Severity:" }),
1084
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: metricValue, children: issue.severity })
1085
+ ] }),
1086
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: metric, children: [
1087
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: metricLabel, children: "Confidence:" }),
1088
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: confidenceWrapper, children: [
1089
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: confidenceBar, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1090
+ "div",
1091
+ {
1092
+ style: {
1093
+ ...confidenceProgress,
1094
+ width: `${issue.confidence * 100}%`
1095
+ },
1096
+ className: `confidence-progress ${getSeverityClass(
1097
+ issue.severity
1098
+ )}`
1099
+ }
1100
+ ) }),
1101
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: confidenceValue, children: [
1102
+ Math.round(issue.confidence * 100),
1103
+ "%"
1104
+ ] })
1105
+ ] })
1106
+ ] })
1107
+ ] }),
1108
+ selectedIssue === issue.id && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: issueDetails, className: "issue-details", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: detailsContent, children: [
1109
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: detailsText, children: [
1110
+ "Rule ID: ",
1111
+ issue.ruleId
1112
+ ] }),
1113
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: detailsDescription, children: issue.reason })
1114
+ ] }) })
1115
+ ] }),
1116
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1117
+ "svg",
1118
+ {
1119
+ style: {
1120
+ ...expandIcon,
1121
+ transform: selectedIssue === issue.id ? "rotate(180deg)" : "rotate(0deg)"
1122
+ },
1123
+ fill: "none",
1124
+ viewBox: "0 0 24 24",
1125
+ stroke: "currentColor",
1126
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1127
+ "path",
1128
+ {
1129
+ strokeLinecap: "round",
1130
+ strokeLinejoin: "round",
1131
+ strokeWidth: 2,
1132
+ d: "M19 9l-7 7-7-7"
1133
+ }
1134
+ )
1135
+ }
1136
+ )
1137
+ ] })
1138
+ },
1139
+ issue.id
1140
+ )) }),
1141
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: footer, children: [
1142
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: footerText, children: "Last scan: just now" }),
1143
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: scanButton, children: "Run Scan" })
1144
+ ] })
1145
+ ] })
1146
+ ] })
1147
+ ] });
1148
+ }
1149
+ var styles = `
1150
+ @keyframes slideUp {
1151
+ from { transform: translateY(20px); opacity: 0; }
1152
+ to { transform: translateY(0); opacity: 1; }
1153
+ }
1154
+
1155
+ @keyframes slideDown {
1156
+ from { max-height: 0; opacity: 0; }
1157
+ to { max-height: 600px; opacity: 1; }
1158
+ }
1159
+
1160
+ @keyframes pulseGlow {
1161
+ 0%, 100% { box-shadow: 0 0 15px rgba(239, 68, 68, 0.3); }
1162
+ 50% { box-shadow: 0 0 25px rgba(239, 68, 68, 0.6); }
1163
+ }
1164
+
1165
+ @keyframes ping {
1166
+ 75%, 100% {
1167
+ transform: scale(2);
1168
+ opacity: 0;
1169
+ }
1170
+ }
1171
+
1172
+ @keyframes pulse {
1173
+ 0%, 100% { opacity: 1; }
1174
+ 50% { opacity: 0.5; }
1175
+ }
1176
+
1177
+ .perfguard-header:hover {
1178
+ background: linear-gradient(to right, #1e293b, #0f172a);
1179
+ }
1180
+
1181
+ .critical-badge {
1182
+ animation: pulseGlow 2s ease-in-out infinite;
1183
+ }
1184
+
1185
+ .status-dot-pulse {
1186
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
1187
+ }
1188
+
1189
+ .status-badge-open {
1190
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
1191
+ }
1192
+
1193
+ .issues-container {
1194
+ animation: slideDown 0.3s ease-out;
1195
+ }
1196
+
1197
+ .issue-card {
1198
+ animation: slideUp 0.3s ease-out;
1199
+ transition: all 0.2s ease;
1200
+ }
1201
+
1202
+ .issue-card:hover {
1203
+ background-color: rgba(30, 41, 59, 0.5);
1204
+ }
1205
+
1206
+ .issue-card.severity-critical {
1207
+ border-left: 4px solid rgba(239, 68, 68, 0.3);
1208
+ background-color: rgba(239, 68, 68, 0.1);
1209
+ }
1210
+
1211
+ .issue-card.severity-high {
1212
+ border-left: 4px solid rgba(249, 115, 22, 0.3);
1213
+ background-color: rgba(249, 115, 22, 0.1);
1214
+ }
1215
+
1216
+ .issue-card.severity-medium {
1217
+ border-left: 4px solid rgba(234, 179, 8, 0.3);
1218
+ background-color: rgba(234, 179, 8, 0.1);
1219
+ }
1220
+
1221
+ .issue-card.severity-low {
1222
+ border-left: 4px solid rgba(59, 130, 246, 0.3);
1223
+ background-color: rgba(59, 130, 246, 0.1);
1224
+ }
1225
+
1226
+ .issue-details {
1227
+ animation: slideDown 0.2s ease-out;
1228
+ }
1229
+
1230
+ .confidence-progress {
1231
+ transition: width 0.5s ease-out;
1232
+ }
1233
+
1234
+ .confidence-progress.severity-critical {
1235
+ background-color: #ef4444;
1236
+ }
1237
+
1238
+ .confidence-progress.severity-high {
1239
+ background-color: #f97316;
1240
+ }
1241
+
1242
+ .confidence-progress.severity-medium {
1243
+ background-color: #eab308;
1244
+ }
1245
+
1246
+ .confidence-progress.severity-low {
1247
+ background-color: #3b82f6;
1248
+ }
1249
+ `;
1250
+ var panel = {
1251
+ position: "fixed",
1252
+ bottom: 16,
1253
+ right: 16,
1254
+ width: 480,
1255
+ fontFamily: "monospace",
1256
+ fontSize: 14,
1257
+ zIndex: 1e4,
1258
+ background: "#0f172a",
1259
+ borderRadius: 8,
1260
+ boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.5)",
1261
+ border: "1px solid #334155",
1262
+ overflow: "hidden",
1263
+ animation: "slideUp 0.3s ease-out"
1264
+ };
1265
+ var header = {
1266
+ padding: "12px 16px",
1267
+ background: "linear-gradient(to right, #1e293b, #0f172a)",
1268
+ borderBottom: "1px solid #334155",
1269
+ cursor: "pointer",
1270
+ display: "flex",
1271
+ alignItems: "center",
1272
+ justifyContent: "space-between",
1273
+ transition: "background 0.2s ease"
1274
+ };
1275
+ var headerLeft = {
1276
+ display: "flex",
1277
+ alignItems: "center",
1278
+ gap: 12
1279
+ };
1280
+ var iconWrapper = {
1281
+ position: "relative"
1282
+ };
1283
+ var icon = {
1284
+ fontSize: 24
1285
+ };
1286
+ var criticalPing = {
1287
+ position: "absolute",
1288
+ top: -4,
1289
+ right: -4,
1290
+ width: 12,
1291
+ height: 12,
1292
+ background: "#ef4444",
1293
+ borderRadius: "50%",
1294
+ animation: "ping 1s cubic-bezier(0, 0, 0.2, 1) infinite"
1295
+ };
1296
+ var headerTitle = {
1297
+ fontWeight: "bold",
1298
+ color: "#ffffff",
1299
+ display: "flex",
1300
+ alignItems: "center",
1301
+ gap: 8
1302
+ };
1303
+ var betaBadge = {
1304
+ fontSize: 10,
1305
+ padding: "2px 8px",
1306
+ background: "rgba(59, 130, 246, 0.2)",
1307
+ color: "#60a5fa",
1308
+ borderRadius: 4,
1309
+ border: "1px solid rgba(59, 130, 246, 0.3)"
1310
+ };
1311
+ var headerSubtitle = {
1312
+ fontSize: 12,
1313
+ color: "#94a3b8"
1314
+ };
1315
+ var headerRight = {
1316
+ display: "flex",
1317
+ alignItems: "center",
1318
+ gap: 8
1319
+ };
1320
+ var criticalBadge = {
1321
+ padding: "4px 8px",
1322
+ fontSize: 12,
1323
+ background: "rgba(239, 68, 68, 0.2)",
1324
+ color: "#f87171",
1325
+ borderRadius: 4,
1326
+ border: "1px solid rgba(239, 68, 68, 0.3)"
1327
+ };
1328
+ var highBadge = {
1329
+ padding: "4px 8px",
1330
+ fontSize: 12,
1331
+ background: "rgba(249, 115, 22, 0.2)",
1332
+ color: "#fb923c",
1333
+ borderRadius: 4,
1334
+ border: "1px solid rgba(249, 115, 22, 0.3)"
1335
+ };
1336
+ var chevron = {
1337
+ width: 20,
1338
+ height: 20,
1339
+ color: "#94a3b8",
1340
+ transition: "transform 0.3s ease"
1341
+ };
1342
+ var issuesContainer = {
1343
+ maxHeight: 500,
1344
+ overflowY: "auto"
1345
+ };
1346
+ var issuesList = {
1347
+ borderTop: "1px solid #1e293b"
1348
+ };
1349
+ var issueCard = (idx) => ({
1350
+ padding: 16,
1351
+ cursor: "pointer",
1352
+ borderBottom: "1px solid #1e293b",
1353
+ animationDelay: `${idx * 50}ms`
1354
+ });
1355
+ var issueContent = {
1356
+ display: "flex",
1357
+ alignItems: "flex-start",
1358
+ justifyContent: "space-between",
1359
+ gap: 12
1360
+ };
1361
+ var issueMain = {
1362
+ flex: 1
1363
+ };
1364
+ var issueHeader = {
1365
+ display: "flex",
1366
+ alignItems: "center",
1367
+ gap: 8,
1368
+ marginBottom: 8
1369
+ };
1370
+ var statusDot = (status) => ({
1371
+ width: 8,
1372
+ height: 8,
1373
+ borderRadius: "50%",
1374
+ background: status === "OPEN" ? "#ef4444" : "#22c55e"
1375
+ });
1376
+ var componentName = {
1377
+ fontWeight: 600,
1378
+ color: "#ffffff"
1379
+ };
1380
+ var statusBadgeResolved = {
1381
+ padding: "2px 8px",
1382
+ fontSize: 12,
1383
+ borderRadius: 9999,
1384
+ background: "rgba(34, 197, 94, 0.2)",
1385
+ color: "#4ade80",
1386
+ border: "1px solid rgba(34, 197, 94, 0.3)"
1387
+ };
1388
+ var statusBadgeOpen = {
1389
+ padding: "2px 8px",
1390
+ fontSize: 12,
1391
+ borderRadius: 9999,
1392
+ background: "rgba(239, 68, 68, 0.2)",
1393
+ color: "#f87171",
1394
+ border: "1px solid rgba(239, 68, 68, 0.3)"
1395
+ };
1396
+ var ruleId = {
1397
+ fontSize: 14,
1398
+ color: "#cbd5e1",
1399
+ marginBottom: 8,
1400
+ fontWeight: 500
1401
+ };
1402
+ var issueMetrics = {
1403
+ display: "flex",
1404
+ alignItems: "center",
1405
+ gap: 16,
1406
+ fontSize: 12
1407
+ };
1408
+ var metric = {
1409
+ display: "flex",
1410
+ alignItems: "center",
1411
+ gap: 4
1412
+ };
1413
+ var metricLabel = {
1414
+ color: "#64748b"
1415
+ };
1416
+ var metricValue = {
1417
+ fontWeight: "bold",
1418
+ color: "#e2e8f0"
1419
+ };
1420
+ var confidenceWrapper = {
1421
+ display: "flex",
1422
+ alignItems: "center",
1423
+ gap: 4
1424
+ };
1425
+ var confidenceBar = {
1426
+ width: 64,
1427
+ height: 6,
1428
+ background: "#334155",
1429
+ borderRadius: 9999,
1430
+ overflow: "hidden"
1431
+ };
1432
+ var confidenceProgress = {
1433
+ height: "100%"
1434
+ };
1435
+ var confidenceValue = {
1436
+ color: "#cbd5e1",
1437
+ fontWeight: 500
1438
+ };
1439
+ var issueDetails = {
1440
+ marginTop: 12,
1441
+ paddingTop: 12,
1442
+ borderTop: "1px solid #334155"
1443
+ };
1444
+ var detailsContent = {
1445
+ display: "flex",
1446
+ flexDirection: "column",
1447
+ // justifyContent: "space-between",
1448
+ alignItems: "flex-start",
1449
+ gap: 2,
1450
+ fontSize: 12,
1451
+ color: "#94a3b8"
1452
+ };
1453
+ var detailsDescription = {
1454
+ color: "#b8b594ff"
1455
+ };
1456
+ var detailsText = {
1457
+ color: "#94a3b8"
1458
+ };
1459
+ var expandIcon = {
1460
+ width: 16,
1461
+ height: 16,
1462
+ color: "#64748b",
1463
+ transition: "transform 0.2s ease",
1464
+ flexShrink: 0,
1465
+ marginTop: 4
1466
+ };
1467
+ var footer = {
1468
+ padding: 12,
1469
+ background: "rgba(30, 41, 59, 0.5)",
1470
+ borderTop: "1px solid #334155",
1471
+ display: "flex",
1472
+ justifyContent: "space-between",
1473
+ alignItems: "center"
1474
+ };
1475
+ var footerText = {
1476
+ fontSize: 12,
1477
+ color: "#94a3b8"
1478
+ };
1479
+ var scanButton = {
1480
+ fontSize: 12,
1481
+ padding: "6px 12px",
1482
+ background: "#3b82f6",
1483
+ color: "#ffffff",
1484
+ border: "none",
1485
+ borderRadius: 4,
1486
+ cursor: "pointer",
1487
+ fontWeight: 500,
1488
+ fontFamily: "monospace",
1489
+ transition: "background 0.2s ease"
1490
+ };
1491
+
1492
+ // src/PerfProvider.tsx
1493
+ var import_jsx_runtime2 = require("react/jsx-runtime");
1494
+ var worker = null;
1495
+ function PerfProvider({ children }) {
1496
+ const [stats, setStats] = (0, import_react2.useState)({ issues: 0, critical: 0 });
1497
+ if (!isDev) {
1498
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
1499
+ }
1500
+ (0, import_react2.useEffect)(() => {
1501
+ try {
1502
+ worker = createAnalyzerWorker();
1503
+ const rules = getRulesConfig();
1504
+ worker.postMessage({
1505
+ type: "INIT_RULES",
1506
+ payload: rules
1507
+ });
1508
+ worker.onmessage = (e) => {
1509
+ const { type, data, hasCritical } = e.data;
1510
+ if (type === "INIT_SUCCESS") {
1511
+ console.log(`\u2705 [PerfGuard] Worker ready with ${e.data.count} rules`);
1512
+ }
1513
+ if (type === "RESULTS") {
1514
+ setStats((prev) => ({
1515
+ issues: prev.issues + data.length,
1516
+ critical: prev.critical + (hasCritical ? 1 : 0)
1517
+ }));
1518
+ data.forEach((result) => {
1519
+ showWarning(result);
1520
+ if (result.hasCritical) {
1521
+ showCriticalAlert(result);
1522
+ }
1523
+ });
1524
+ }
1525
+ if (type === "STATS") {
1526
+ console.log("\u{1F4CA} [PerfGuard] Stats:", e.data.data);
1527
+ }
1528
+ };
1529
+ worker.onerror = (err) => {
1530
+ console.error("[PerfGuard] Worker error:", err);
1531
+ };
1532
+ } catch (err) {
1533
+ console.warn("[PerfGuard] Worker failed to start", err);
1534
+ return;
1535
+ }
1536
+ const interval = setInterval(() => {
1537
+ const data = flushMetrics();
1538
+ if (data.length) {
1539
+ console.log(`[PerfGuard] Flushing ${data.length} snapshot(s)`);
1540
+ worker?.postMessage({
1541
+ type: "EVALUATE",
1542
+ payload: data
1543
+ });
1544
+ }
1545
+ }, 5e3);
1546
+ const statsInterval = setInterval(() => {
1547
+ worker?.postMessage({ type: "GET_STATS" });
1548
+ }, 3e4);
1549
+ return () => {
1550
+ clearInterval(interval);
1551
+ clearInterval(statsInterval);
1552
+ worker?.terminate();
1553
+ worker = null;
1554
+ };
1555
+ }, []);
1556
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1557
+ children,
1558
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PerfGuardPanel, {})
1559
+ ] });
1560
+ }
1561
+
1562
+ // src/withPerfGuard.tsx
1563
+ var import_react3 = require("react");
1564
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1565
+ var DEFAULT_OPTIONS = {
1566
+ boundaryType: "INLINE",
1567
+ enabled: true
1568
+ };
1569
+ function withPerfGuard(Component, options) {
1570
+ const { boundaryType, enabled } = {
1571
+ ...DEFAULT_OPTIONS,
1572
+ ...options
1573
+ };
1574
+ const fallbackId = "AnonymousComponent";
1575
+ const name = Component.displayName || Component.name || `PerfGuard(${fallbackId})`;
1576
+ if (process.env.NODE_ENV === "production" || !enabled) {
1577
+ return Component;
1578
+ }
1579
+ const Guarded = (props) => {
1580
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1581
+ import_react3.Profiler,
1582
+ {
1583
+ id: name,
1584
+ onRender: (id, phase, actualDuration, baseDuration, startTime, commitTime) => {
1585
+ collectMetric({
1586
+ component: id,
1587
+ phase,
1588
+ actualDuration,
1589
+ baseDuration,
1590
+ startTime,
1591
+ commitTime,
1592
+ boundaryType
1593
+ });
1594
+ },
1595
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Component, { ...props })
1596
+ }
1597
+ );
1598
+ };
1599
+ Guarded.displayName = `withPerfGuard(${name})`;
1600
+ return Guarded;
1601
+ }
1602
+
1603
+ // src/PerfProfiler.tsx
1604
+ var import_react4 = require("react");
1605
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1606
+ function PerfProfiler({
1607
+ id,
1608
+ children,
1609
+ boundaryType = "INLINE"
1610
+ }) {
1611
+ if (!isDev) {
1612
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children });
1613
+ }
1614
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1615
+ import_react4.Profiler,
1616
+ {
1617
+ id,
1618
+ onRender: (component, phase, actualDuration, baseDuration, startTime, commitTime) => {
1619
+ collectMetric({
1620
+ component,
1621
+ phase,
1622
+ actualDuration,
1623
+ baseDuration,
1624
+ startTime,
1625
+ commitTime,
1626
+ boundaryType
1627
+ });
1628
+ },
1629
+ children
1630
+ }
1631
+ );
1632
+ }
1633
+ // Annotate the CommonJS export names for ESM import in node:
1634
+ 0 && (module.exports = {
1635
+ PerfProfiler,
1636
+ PerfProvider,
1637
+ withPerfGuard
1638
+ });