react-state-basis 0.6.6 → 0.6.7
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 +2 -2
- package/dist/{chunk-WMHY2C6D.mjs → chunk-KIXM6YRX.mjs} +179 -137
- package/dist/chunk-KIXM6YRX.mjs.map +1 -0
- package/dist/index.js +178 -136
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/integrations/zustand.js +173 -135
- package/dist/integrations/zustand.js.map +1 -1
- package/dist/integrations/zustand.mjs +1 -1
- package/package.json +1 -1
- package/dist/chunk-WMHY2C6D.mjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -351,6 +351,10 @@ var shouldLog = (key) => {
|
|
|
351
351
|
return false;
|
|
352
352
|
};
|
|
353
353
|
var isBooleanLike = (name) => /^(is|has|can|should|did|will|show|hide)(?=[A-Z_])/.test(name);
|
|
354
|
+
var displayName = (raw) => {
|
|
355
|
+
const { name } = parseLabel(raw);
|
|
356
|
+
return name.replace(/:\d+$/, "");
|
|
357
|
+
};
|
|
354
358
|
var areSyncSignificant = (metaA, metaB) => {
|
|
355
359
|
const { kSync, densityA, densityB } = countOverlapsCircular(
|
|
356
360
|
metaA.buffer,
|
|
@@ -362,95 +366,80 @@ var areSyncSignificant = (metaA, metaB) => {
|
|
|
362
366
|
};
|
|
363
367
|
var getSuggestedFix = (issue, info) => {
|
|
364
368
|
if (issue.label.includes("Global Event")) {
|
|
365
|
-
return `
|
|
369
|
+
return `One interaction is updating state in several places. If that is really one transition, put it in one %cstore / reducer%c. If it is intentional, ignore this.`;
|
|
366
370
|
}
|
|
367
371
|
const violations = issue.violations || [];
|
|
368
372
|
const leaks = violations.filter((v) => v.type === "causal_leak");
|
|
369
373
|
const mirrors = violations.filter((v) => v.type === "context_mirror");
|
|
370
374
|
const duplicates = violations.filter((v) => v.type === "duplicate_state");
|
|
371
375
|
if (mirrors.length > 0) {
|
|
372
|
-
return `
|
|
376
|
+
return `A local hook is only tracking context or a store. If it is not a draft, delete it and read the %ccontext / store%c in render.`;
|
|
373
377
|
}
|
|
374
378
|
if (leaks.length > 0) {
|
|
375
|
-
const targetName =
|
|
379
|
+
const targetName = displayName(leaks[0].target);
|
|
376
380
|
if (issue.label.includes("effect")) {
|
|
377
|
-
return `
|
|
381
|
+
return `An effect is calling setState on ${targetName}, which paints again. If you can compute ${targetName} while rendering, drop the %ceffect%c.`;
|
|
378
382
|
}
|
|
379
|
-
return
|
|
383
|
+
return `${info.name} updates, then ${targetName} updates on the next frame. If they are one fact, write them in the same %csetState%c.`;
|
|
380
384
|
}
|
|
381
385
|
if (duplicates.length > 0) {
|
|
382
386
|
if (isBooleanLike(info.name)) {
|
|
383
|
-
return `
|
|
387
|
+
return `Several flags move together. One %cstatus%c value avoids impossible combinations.`;
|
|
384
388
|
}
|
|
385
|
-
return `
|
|
389
|
+
return `These hooks move together. If one is just the other in another shape, compute it while %crendering%c.`;
|
|
386
390
|
}
|
|
387
391
|
if (issue.metric === "density") {
|
|
388
|
-
return `
|
|
392
|
+
return `This hook updates faster than a frame. %cDebounce%c it or keep it in a ref if the UI does not need every pulse.`;
|
|
389
393
|
}
|
|
390
|
-
return `
|
|
394
|
+
return `Inspect ${info.name} and what updates with it.`;
|
|
391
395
|
};
|
|
392
396
|
var displayHealthReport = (history2, violationMap) => {
|
|
393
397
|
if (!isWeb) return;
|
|
394
398
|
const entries = Array.from(history2.entries());
|
|
395
399
|
if (entries.length === 0) return;
|
|
396
400
|
const topIssues = identifyTopIssues(instance.graph, history2, instance.redundantLabels, violationMap);
|
|
397
|
-
console.group(`%c
|
|
401
|
+
console.group(`%c BASIS | report `, STYLES.headerIdentity);
|
|
398
402
|
if (topIssues.length > 0) {
|
|
399
|
-
console.log(
|
|
400
|
-
`%c\u{1F3AF} REFACTOR PRIORITIES %c(PRIME MOVERS)`,
|
|
401
|
-
`font-weight: bold; color: ${THEME.identity}; margin-top: 10px;`,
|
|
402
|
-
`font-weight: normal; color: ${THEME.muted}; font-style: italic;`
|
|
403
|
-
);
|
|
403
|
+
console.log(`%cStart here`, `font-weight: bold; color: ${THEME.identity}; margin-top: 10px;`);
|
|
404
404
|
topIssues.forEach((issue, idx) => {
|
|
405
405
|
const info = parseLabel(issue.label);
|
|
406
|
-
const icon = issue.metric === "influence" ? "\
|
|
406
|
+
const icon = issue.metric === "influence" ? "\u2192" : "\u2022";
|
|
407
407
|
const pColor = idx === 0 ? THEME.problem : idx === 1 ? THEME.solution : THEME.identity;
|
|
408
|
-
let displayName = info.name;
|
|
409
|
-
let displayFile = info.file;
|
|
410
|
-
if (issue.label.includes("Global Event")) {
|
|
411
|
-
displayName = info.name;
|
|
412
|
-
displayFile = info.file;
|
|
413
|
-
}
|
|
414
408
|
console.group(
|
|
415
|
-
` %c${idx + 1}%c ${icon} ${displayName} %c(${
|
|
409
|
+
` %c${idx + 1}%c ${icon} ${displayName(issue.label)} %c(${info.file})`,
|
|
416
410
|
`background: ${pColor}; color: ${idx === 1 ? "black" : "white"}; border-radius: 50%; padding: 0 5px;`,
|
|
417
411
|
"font-family: monospace; font-weight: 700;",
|
|
418
|
-
`color: ${THEME.muted}; font-size: 10px; font-weight: normal
|
|
412
|
+
`color: ${THEME.muted}; font-size: 10px; font-weight: normal;`
|
|
419
413
|
);
|
|
420
|
-
console.log(`%c${issue.reason}`, `color: ${THEME.muted}
|
|
414
|
+
console.log(`%c${issue.reason}`, `color: ${THEME.muted};`);
|
|
421
415
|
if (issue.violations.length > 0) {
|
|
422
416
|
const byFile = /* @__PURE__ */ new Map();
|
|
423
417
|
issue.violations.forEach((v) => {
|
|
424
418
|
if (issue.label.includes("Global Event") && v.type === "context_mirror") return;
|
|
425
419
|
const { file, name } = parseLabel(v.target);
|
|
426
420
|
if (!byFile.has(file)) byFile.set(file, []);
|
|
427
|
-
byFile.get(file).push(name);
|
|
421
|
+
byFile.get(file).push(name.replace(/:\d+$/, ""));
|
|
428
422
|
});
|
|
429
423
|
const impactParts = [];
|
|
430
424
|
byFile.forEach((vars, file) => {
|
|
431
|
-
|
|
432
|
-
impactParts.push(`${file} (${varList})`);
|
|
425
|
+
impactParts.push(`${file} (${vars.join(", ")})`);
|
|
433
426
|
});
|
|
434
427
|
if (impactParts.length > 0) {
|
|
435
|
-
console.log(`%
|
|
428
|
+
console.log(`%cAlso updates: %c${impactParts.join(" \xB7 ")}`, STYLES.impactLabel, "");
|
|
436
429
|
}
|
|
437
430
|
}
|
|
438
431
|
const fix = getSuggestedFix(issue, info);
|
|
439
432
|
const fixParts = fix.split("%c");
|
|
440
433
|
if (fixParts.length === 3) {
|
|
441
434
|
console.log(
|
|
442
|
-
`%
|
|
435
|
+
`%cTry: %c${fixParts[0]}%c${fixParts[1]}%c${fixParts[2]}`,
|
|
443
436
|
STYLES.actionLabel,
|
|
444
437
|
"",
|
|
445
438
|
STYLES.actionPill,
|
|
446
439
|
""
|
|
447
440
|
);
|
|
448
441
|
} else {
|
|
449
|
-
console.log(
|
|
450
|
-
`%cSolution: %c${fix}`,
|
|
451
|
-
STYLES.actionLabel,
|
|
452
|
-
""
|
|
453
|
-
);
|
|
442
|
+
console.log(`%cTry: %c${fix}`, STYLES.actionLabel, "");
|
|
454
443
|
}
|
|
455
444
|
console.groupEnd();
|
|
456
445
|
});
|
|
@@ -474,135 +463,170 @@ var displayHealthReport = (history2, violationMap) => {
|
|
|
474
463
|
else independentCount++;
|
|
475
464
|
});
|
|
476
465
|
const totalVars = entries.length;
|
|
477
|
-
const redundancyScore = (independentCount + clusters.length) / totalVars * 100;
|
|
478
|
-
let internalEdges = 0;
|
|
479
|
-
instance.graph.forEach((targets, source) => {
|
|
480
|
-
if (source.startsWith("Event_Tick_")) return;
|
|
481
|
-
internalEdges += targets.size;
|
|
482
|
-
});
|
|
483
|
-
const causalPenalty = internalEdges / totalVars * 100;
|
|
484
|
-
let healthScore = redundancyScore - causalPenalty;
|
|
485
|
-
if (healthScore < 0) healthScore = 0;
|
|
486
|
-
const scoreColor = healthScore > 85 ? THEME.success : THEME.problem;
|
|
487
466
|
console.log(
|
|
488
|
-
`%
|
|
489
|
-
STYLES.
|
|
490
|
-
`color: ${scoreColor}; font-weight: bold;`
|
|
467
|
+
`%c${independentCount + clusters.length} of ${totalVars} instrumented hooks look independent in this window.`,
|
|
468
|
+
STYLES.subText
|
|
491
469
|
);
|
|
492
|
-
console.log(`%cSources of Truth: ${independentCount + clusters.length}/${totalVars} | Causal Leaks: ${internalEdges}`, STYLES.subText);
|
|
493
470
|
if (clusters.length > 0) {
|
|
494
|
-
console.log(
|
|
471
|
+
console.log(
|
|
472
|
+
`%c${clusters.length} group${clusters.length === 1 ? "" : "s"} that keep updating together:`,
|
|
473
|
+
`font-weight: bold; color: ${THEME.problem}; margin-top: 10px;`
|
|
474
|
+
);
|
|
495
475
|
clusters.forEach((cluster, idx) => {
|
|
496
476
|
const clusterMetas = cluster.map((l) => ({
|
|
497
477
|
label: l,
|
|
498
478
|
meta: history2.get(l),
|
|
499
|
-
name:
|
|
479
|
+
name: displayName(l)
|
|
500
480
|
}));
|
|
501
481
|
const hasCtx = clusterMetas.some(
|
|
502
482
|
(c) => c.meta.role === "context" /* CONTEXT */ || c.meta.role === "store" /* STORE */
|
|
503
483
|
);
|
|
504
|
-
const names = clusterMetas.map((c) =>
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
484
|
+
const names = clusterMetas.map((c) => c.name).join(", ");
|
|
485
|
+
console.group(
|
|
486
|
+
` %c${idx + 1}%c ${names}`,
|
|
487
|
+
`background: ${THEME.problem}; color: white; border-radius: 50%; padding: 0 5px;`,
|
|
488
|
+
"font-family: monospace; font-weight: bold;"
|
|
489
|
+
);
|
|
509
490
|
if (hasCtx) {
|
|
510
491
|
const hasStore = clusterMetas.some((c) => c.meta.role === "store" /* STORE */);
|
|
511
|
-
const sourceType = hasStore ? "
|
|
512
|
-
console.log(
|
|
513
|
-
console.log(
|
|
492
|
+
const sourceType = hasStore ? "a store" : "context";
|
|
493
|
+
console.log(`A local hook is only following ${sourceType}.`);
|
|
494
|
+
console.log(
|
|
495
|
+
`%cTry:%c Read ${sourceType} in render if the local value is not a draft.`,
|
|
496
|
+
STYLES.actionLabel,
|
|
497
|
+
""
|
|
498
|
+
);
|
|
514
499
|
} else {
|
|
515
|
-
const boolKeywords = [
|
|
500
|
+
const boolKeywords = [
|
|
501
|
+
"is",
|
|
502
|
+
"has",
|
|
503
|
+
"can",
|
|
504
|
+
"should",
|
|
505
|
+
"loading",
|
|
506
|
+
"success",
|
|
507
|
+
"error",
|
|
508
|
+
"active",
|
|
509
|
+
"enabled",
|
|
510
|
+
"open",
|
|
511
|
+
"visible"
|
|
512
|
+
];
|
|
516
513
|
const boolCount = clusterMetas.filter(
|
|
517
514
|
(c) => boolKeywords.some((kw) => c.name.toLowerCase().startsWith(kw))
|
|
518
515
|
).length;
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
console.log(
|
|
522
|
-
|
|
516
|
+
if (cluster.length > 2 && boolCount / cluster.length > 0.5) {
|
|
517
|
+
console.log(`These flags move together.`);
|
|
518
|
+
console.log(
|
|
519
|
+
`%cTry:%c One %cstatus%c instead of several booleans.`,
|
|
520
|
+
STYLES.actionLabel,
|
|
521
|
+
"",
|
|
522
|
+
STYLES.actionPill,
|
|
523
|
+
""
|
|
524
|
+
);
|
|
523
525
|
} else if (cluster.length > 2) {
|
|
524
|
-
console.log(
|
|
525
|
-
console.log(
|
|
526
|
+
console.log(`These hooks move on the same frames. Often the same click or fetch.`);
|
|
527
|
+
console.log(
|
|
528
|
+
`%cTry:%c Leave it if that is intentional. Otherwise one %creducer%c.`,
|
|
529
|
+
STYLES.actionLabel,
|
|
530
|
+
"",
|
|
531
|
+
STYLES.actionPill,
|
|
532
|
+
""
|
|
533
|
+
);
|
|
526
534
|
} else {
|
|
527
|
-
console.log(
|
|
528
|
-
console.log(
|
|
535
|
+
console.log(`These two hooks keep updating in the same frame.`);
|
|
536
|
+
console.log(
|
|
537
|
+
`%cTry:%c If one is derived, compute it while %crendering%c.`,
|
|
538
|
+
STYLES.actionLabel,
|
|
539
|
+
"",
|
|
540
|
+
STYLES.actionPill,
|
|
541
|
+
""
|
|
542
|
+
);
|
|
529
543
|
}
|
|
530
544
|
}
|
|
531
545
|
console.groupEnd();
|
|
532
546
|
});
|
|
533
547
|
} else {
|
|
534
|
-
console.log(
|
|
548
|
+
console.log(
|
|
549
|
+
"%cNo hooks were updating in lockstep in this window.",
|
|
550
|
+
`color: ${THEME.success}; font-weight: bold;`
|
|
551
|
+
);
|
|
535
552
|
}
|
|
536
553
|
console.groupEnd();
|
|
537
554
|
};
|
|
538
|
-
var displayRedundancyAlert = (labelA, metaA, labelB, metaB,
|
|
555
|
+
var displayRedundancyAlert = (labelA, metaA, labelB, metaB, overlap) => {
|
|
539
556
|
if (!isWeb || !shouldLog(`redundant-${labelA}-${labelB}`)) return;
|
|
540
557
|
const infoA = parseLabel(labelA);
|
|
541
|
-
const
|
|
558
|
+
const nameA = displayName(labelA);
|
|
559
|
+
const nameB = displayName(labelB);
|
|
542
560
|
const isContextMirror = metaA.role === "local" /* LOCAL */ && metaB.role === "context" /* CONTEXT */ || metaB.role === "local" /* LOCAL */ && metaA.role === "context" /* CONTEXT */;
|
|
543
561
|
const isStoreMirror = metaA.role === "local" /* LOCAL */ && metaB.role === "store" /* STORE */ || metaB.role === "local" /* LOCAL */ && metaA.role === "store" /* STORE */;
|
|
544
|
-
const alertType = isContextMirror ? "
|
|
545
|
-
|
|
546
|
-
console.
|
|
547
|
-
console.log(`%
|
|
562
|
+
const alertType = isContextMirror ? "local state follows context" : isStoreMirror ? "local state follows a store" : "hooks moving together";
|
|
563
|
+
const times = overlap.kSync === 1 ? "time" : "times";
|
|
564
|
+
console.group(`%c BASIS | ${alertType} `, STYLES.headerProblem);
|
|
565
|
+
console.log(`%c${infoA.file}`, STYLES.location);
|
|
566
|
+
console.log(
|
|
567
|
+
`%c${nameA}%c and %c${nameB}%c updated in the same frame ${overlap.kSync} ${times}.`,
|
|
568
|
+
STYLES.label,
|
|
569
|
+
"",
|
|
570
|
+
STYLES.label,
|
|
571
|
+
""
|
|
572
|
+
);
|
|
548
573
|
if (isContextMirror || isStoreMirror) {
|
|
549
|
-
const sourceType = isStoreMirror ? "
|
|
574
|
+
const sourceType = isStoreMirror ? "store" : "context";
|
|
550
575
|
console.log(
|
|
551
|
-
`%
|
|
576
|
+
`%cTry:%c If this is not a draft, delete the local hook and read the %c${sourceType}%c in render.`,
|
|
577
|
+
STYLES.bold,
|
|
578
|
+
"",
|
|
579
|
+
STYLES.actionPill,
|
|
580
|
+
""
|
|
581
|
+
);
|
|
582
|
+
} else if (isBooleanLike(nameA) || isBooleanLike(nameB)) {
|
|
583
|
+
console.log(
|
|
584
|
+
`%cTry:%c One %cstatus%c instead of several flags.`,
|
|
552
585
|
STYLES.bold,
|
|
553
586
|
"",
|
|
554
587
|
STYLES.actionPill,
|
|
555
588
|
""
|
|
556
589
|
);
|
|
557
590
|
} else {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
);
|
|
568
|
-
} else {
|
|
569
|
-
console.log(
|
|
570
|
-
`%cFix:%c Redundant State detected. Derive %c${infoB.name}%c from %c${infoA.name}%c during render, or use %cuseMemo%c.`,
|
|
571
|
-
STYLES.bold,
|
|
572
|
-
"",
|
|
573
|
-
STYLES.label,
|
|
574
|
-
"",
|
|
575
|
-
STYLES.label,
|
|
576
|
-
"",
|
|
577
|
-
STYLES.actionPill,
|
|
578
|
-
""
|
|
579
|
-
);
|
|
580
|
-
}
|
|
591
|
+
console.log(
|
|
592
|
+
`%cTry:%c If %c${nameB}%c is just %c${nameA}%c in another shape, compute it while rendering.`,
|
|
593
|
+
STYLES.bold,
|
|
594
|
+
"",
|
|
595
|
+
STYLES.label,
|
|
596
|
+
"",
|
|
597
|
+
STYLES.label,
|
|
598
|
+
""
|
|
599
|
+
);
|
|
581
600
|
}
|
|
582
601
|
console.groupEnd();
|
|
583
602
|
};
|
|
584
|
-
var displayCausalHint = (targetLabel,
|
|
603
|
+
var displayCausalHint = (targetLabel, _targetMeta, sourceLabel, sourceMeta) => {
|
|
585
604
|
if (!isWeb || !shouldLog(`causal-${sourceLabel}-${targetLabel}`)) return;
|
|
586
605
|
const target = parseLabel(targetLabel);
|
|
587
|
-
const
|
|
588
|
-
const
|
|
606
|
+
const sourceName = displayName(sourceLabel);
|
|
607
|
+
const targetName = displayName(targetLabel);
|
|
608
|
+
const headerType = sourceMeta.role === "context" /* CONTEXT */ ? "extra render from context" : sourceMeta.role === "store" /* STORE */ ? "extra render from a store" : "extra render";
|
|
589
609
|
const isEffect = sourceLabel.includes("effect") || sourceLabel.includes("useLayoutEffect");
|
|
590
|
-
console.groupCollapsed(`%c
|
|
591
|
-
console.log(`%c
|
|
592
|
-
console.log(
|
|
610
|
+
console.groupCollapsed(`%c BASIS | ${headerType} `, STYLES.headerProblem);
|
|
611
|
+
console.log(`%c${target.file}`, STYLES.location);
|
|
612
|
+
console.log(
|
|
613
|
+
`%c${sourceName}%c updates %c${targetName}%c on the next frame.`,
|
|
614
|
+
STYLES.label,
|
|
615
|
+
"",
|
|
616
|
+
STYLES.label,
|
|
617
|
+
""
|
|
618
|
+
);
|
|
593
619
|
if (isEffect) {
|
|
594
620
|
console.log(
|
|
595
|
-
`%
|
|
621
|
+
`%cTry:%c If %c${targetName}%c can be computed while rendering, drop the extra setState.`,
|
|
596
622
|
STYLES.bold,
|
|
597
623
|
"",
|
|
598
624
|
STYLES.label,
|
|
599
|
-
"",
|
|
600
|
-
STYLES.actionPill,
|
|
601
625
|
""
|
|
602
626
|
);
|
|
603
627
|
} else {
|
|
604
628
|
console.log(
|
|
605
|
-
`%
|
|
629
|
+
`%cTry:%c Write %c${targetName}%c in the same update as %c${sourceName}%c if they are one fact.`,
|
|
606
630
|
STYLES.bold,
|
|
607
631
|
"",
|
|
608
632
|
STYLES.label,
|
|
@@ -635,7 +659,7 @@ var displayGraphReport = (graph) => {
|
|
|
635
659
|
if (!isWeb) return;
|
|
636
660
|
if (graph.nodes.length === 0) {
|
|
637
661
|
console.log(
|
|
638
|
-
`%c
|
|
662
|
+
`%c BASIS | update graph %c(nothing recorded yet)`,
|
|
639
663
|
STYLES.headerIdentity,
|
|
640
664
|
`color: ${THEME.muted}; font-style: italic;`
|
|
641
665
|
);
|
|
@@ -654,17 +678,22 @@ var displayGraphReport = (graph) => {
|
|
|
654
678
|
occurrences: g.occurrences
|
|
655
679
|
}));
|
|
656
680
|
const groupedSourceIds = new Set(graph.eventGroups.flatMap((g) => g.sourceIds));
|
|
657
|
-
const nonEventGroups = Array.from(outgoing.keys()).filter((id) => !groupedSourceIds.has(id)).map((id) => ({
|
|
681
|
+
const nonEventGroups = Array.from(outgoing.keys()).filter((id) => !groupedSourceIds.has(id)).map((id) => ({
|
|
682
|
+
sourceIds: [id],
|
|
683
|
+
sourceNode: nodeById.get(id),
|
|
684
|
+
edges: outgoing.get(id),
|
|
685
|
+
occurrences: 1
|
|
686
|
+
}));
|
|
658
687
|
const groups = [...eventGroups, ...nonEventGroups].sort(
|
|
659
688
|
(a, b) => b.edges.length - a.edges.length || b.occurrences - a.occurrences
|
|
660
689
|
);
|
|
661
690
|
console.group(
|
|
662
|
-
`%c
|
|
691
|
+
`%c BASIS | update graph %c${graph.nodes.length} nodes \xB7 ${graph.edges.length} edges \xB7 ${groups.length} sources \xB7 last ${graph.bufferWindowSize} frames`,
|
|
663
692
|
STYLES.headerIdentity,
|
|
664
|
-
`color: ${THEME.muted}; font-weight: normal
|
|
693
|
+
`color: ${THEME.muted}; font-weight: normal;`
|
|
665
694
|
);
|
|
666
695
|
console.log(
|
|
667
|
-
`%cparent \u2192 child =
|
|
696
|
+
`%cparent \u2192 child = what we saw cause an update. (\xD7N) = times in this window. Repeat clicks with the same targets are grouped.`,
|
|
668
697
|
STYLES.subText
|
|
669
698
|
);
|
|
670
699
|
groups.forEach((group) => {
|
|
@@ -672,12 +701,12 @@ var displayGraphReport = (graph) => {
|
|
|
672
701
|
const isCtx = group.sourceNode?.role === "context" /* CONTEXT */;
|
|
673
702
|
const isFx = group.sourceNode?.role === "effect";
|
|
674
703
|
const isUnknown = group.sourceNode?.role === "unknown";
|
|
675
|
-
const icon = isEvent ? "\
|
|
704
|
+
const icon = isEvent ? "\u2022" : isCtx ? "ctx" : isFx ? "fx" : isUnknown ? "?" : "\u2022";
|
|
676
705
|
const color = isEvent ? THEME.solution : isCtx ? THEME.context : THEME.identity;
|
|
677
706
|
const fanout = group.edges.length;
|
|
678
707
|
const hits = group.occurrences;
|
|
679
708
|
const hitLabel = hits > 1 ? ` \xB7 \xD7${hits}` : "";
|
|
680
|
-
const title = isEvent ? `
|
|
709
|
+
const title = isEvent ? `click / event \xB7 ${fanout} update${fanout === 1 ? "" : "s"}${hitLabel}` : formatNode(group.sourceNode, group.sourceIds[0]);
|
|
681
710
|
console.groupCollapsed(
|
|
682
711
|
`%c${icon} %c${title}`,
|
|
683
712
|
`color: ${color};`,
|
|
@@ -689,16 +718,16 @@ var displayGraphReport = (graph) => {
|
|
|
689
718
|
const weight = edge.weight > 1 ? ` (\xD7${edge.weight})` : "";
|
|
690
719
|
if (target?.redundant) {
|
|
691
720
|
console.log(
|
|
692
|
-
`%c ${label}%c${weight} %
|
|
721
|
+
`%c ${label}%c${weight} %cmoving with another hook`,
|
|
693
722
|
`color: ${THEME.muted}; font-family: monospace;`,
|
|
694
|
-
`color: ${THEME.muted}
|
|
723
|
+
`color: ${THEME.muted};`,
|
|
695
724
|
`color: ${THEME.problem}; font-weight: bold;`
|
|
696
725
|
);
|
|
697
726
|
} else {
|
|
698
727
|
console.log(
|
|
699
728
|
`%c ${label}%c${weight}`,
|
|
700
729
|
`color: ${THEME.muted}; font-family: monospace;`,
|
|
701
|
-
`color: ${THEME.muted}
|
|
730
|
+
`color: ${THEME.muted};`
|
|
702
731
|
);
|
|
703
732
|
}
|
|
704
733
|
});
|
|
@@ -706,19 +735,26 @@ var displayGraphReport = (graph) => {
|
|
|
706
735
|
});
|
|
707
736
|
console.groupEnd();
|
|
708
737
|
};
|
|
709
|
-
var displayViolentBreaker = (label, count,
|
|
738
|
+
var displayViolentBreaker = (label, count, _threshold) => {
|
|
710
739
|
if (!isWeb) return;
|
|
711
|
-
const
|
|
712
|
-
console.group(`%c
|
|
713
|
-
console.error(
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
console.log(
|
|
740
|
+
const name = displayName(label);
|
|
741
|
+
console.group(`%c BASIS | loop guard `, STYLES.headerProblem);
|
|
742
|
+
console.error(
|
|
743
|
+
`${name} updated ${count} times in one second. Basis stopped recording this path so the tab stays usable.`
|
|
744
|
+
);
|
|
745
|
+
console.log(
|
|
746
|
+
`%cReact may still error on its own. Fix the effect that writes a value it also lists as a dependency.`,
|
|
747
|
+
`color: ${THEME.muted};`
|
|
748
|
+
);
|
|
717
749
|
console.groupEnd();
|
|
718
750
|
};
|
|
719
751
|
var displayBootLog = (windowSize) => {
|
|
720
752
|
if (!isWeb) return;
|
|
721
|
-
console.log(
|
|
753
|
+
console.log(
|
|
754
|
+
`%cBasis%c watching updates (${windowSize}-frame window)`,
|
|
755
|
+
STYLES.basis,
|
|
756
|
+
`color: ${THEME.muted}; margin-left: 8px;`
|
|
757
|
+
);
|
|
722
758
|
};
|
|
723
759
|
|
|
724
760
|
// src/core/analysis.ts
|
|
@@ -759,6 +795,12 @@ var calculateAllSimilarities = (entryA, entryB) => {
|
|
|
759
795
|
significantLead
|
|
760
796
|
};
|
|
761
797
|
};
|
|
798
|
+
var overlapFrom = (s) => ({
|
|
799
|
+
kSync: s.kSync,
|
|
800
|
+
densityA: s.densityA,
|
|
801
|
+
densityB: s.densityB,
|
|
802
|
+
cosine: s.sync
|
|
803
|
+
});
|
|
762
804
|
var shouldSkipComparison = (entryA, entryB, dirtyLabels2) => {
|
|
763
805
|
if (entryA.label === entryB.label) return true;
|
|
764
806
|
if (isSameField(entryA.label, entryB.label)) return true;
|
|
@@ -783,21 +825,21 @@ var detectRedundancy = (entryA, entryB, similarities, redundantSet, violationMap
|
|
|
783
825
|
const roleB = entryB.meta.role;
|
|
784
826
|
if (isGlobalSource(roleA) && isGlobalSource(roleB)) return;
|
|
785
827
|
if (similarities.densityA < 2 || similarities.densityB < 2) return;
|
|
786
|
-
const
|
|
828
|
+
const overlap = overlapFrom(similarities);
|
|
787
829
|
if (roleA === "local" /* LOCAL */ && isGlobalSource(roleB)) {
|
|
788
830
|
redundantSet.add(entryA.label);
|
|
789
|
-
pushViolation(violationMap, entryB.label, { type: "context_mirror", target: entryA.label,
|
|
790
|
-
displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta,
|
|
831
|
+
pushViolation(violationMap, entryB.label, { type: "context_mirror", target: entryA.label, overlap });
|
|
832
|
+
displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta, overlap);
|
|
791
833
|
} else if (isGlobalSource(roleA) && roleB === "local" /* LOCAL */) {
|
|
792
834
|
redundantSet.add(entryB.label);
|
|
793
|
-
pushViolation(violationMap, entryA.label, { type: "context_mirror", target: entryB.label,
|
|
794
|
-
displayRedundancyAlert(entryB.label, entryB.meta, entryA.label, entryA.meta,
|
|
835
|
+
pushViolation(violationMap, entryA.label, { type: "context_mirror", target: entryB.label, overlap });
|
|
836
|
+
displayRedundancyAlert(entryB.label, entryB.meta, entryA.label, entryA.meta, overlap);
|
|
795
837
|
} else if (roleA === "local" /* LOCAL */ && roleB === "local" /* LOCAL */) {
|
|
796
838
|
redundantSet.add(entryA.label);
|
|
797
839
|
redundantSet.add(entryB.label);
|
|
798
|
-
pushViolation(violationMap, entryA.label, { type: "duplicate_state", target: entryB.label,
|
|
799
|
-
pushViolation(violationMap, entryB.label, { type: "duplicate_state", target: entryA.label,
|
|
800
|
-
displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta,
|
|
840
|
+
pushViolation(violationMap, entryA.label, { type: "duplicate_state", target: entryB.label, overlap });
|
|
841
|
+
pushViolation(violationMap, entryB.label, { type: "duplicate_state", target: entryA.label, overlap });
|
|
842
|
+
displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta, overlap);
|
|
801
843
|
}
|
|
802
844
|
};
|
|
803
845
|
var detectCausalLeak = (entryA, entryB, similarities, violationMap, graph) => {
|