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.
@@ -311,6 +311,10 @@ var shouldLog = (key) => {
311
311
  return false;
312
312
  };
313
313
  var isBooleanLike = (name) => /^(is|has|can|should|did|will|show|hide)(?=[A-Z_])/.test(name);
314
+ var displayName = (raw) => {
315
+ const { name } = parseLabel(raw);
316
+ return name.replace(/:\d+$/, "");
317
+ };
314
318
  var areSyncSignificant = (metaA, metaB) => {
315
319
  const { kSync, densityA, densityB } = countOverlapsCircular(
316
320
  metaA.buffer,
@@ -322,95 +326,80 @@ var areSyncSignificant = (metaA, metaB) => {
322
326
  };
323
327
  var getSuggestedFix = (issue, info) => {
324
328
  if (issue.label.includes("Global Event")) {
325
- return `These variables update together but live in different hooks/files. Consolidate them into a single %cuseReducer%c or atomic store update.`;
329
+ 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.`;
326
330
  }
327
331
  const violations = issue.violations || [];
328
332
  const leaks = violations.filter((v) => v.type === "causal_leak");
329
333
  const mirrors = violations.filter((v) => v.type === "context_mirror");
330
334
  const duplicates = violations.filter((v) => v.type === "duplicate_state");
331
335
  if (mirrors.length > 0) {
332
- return `Local state is 'shadowing' Global Context. This creates two sources of truth. Delete the local state and consume the %cContext%c value directly.`;
336
+ 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.`;
333
337
  }
334
338
  if (leaks.length > 0) {
335
- const targetName = parseLabel(leaks[0].target).name;
339
+ const targetName = displayName(leaks[0].target);
336
340
  if (issue.label.includes("effect")) {
337
- return `This Effect triggers a synchronous re-render of ${targetName}. Calculate ${targetName} during the render phase (Derived State) or wrap in %cuseMemo%c if expensive.`;
341
+ return `An effect is calling setState on ${targetName}, which paints again. If you can compute ${targetName} while rendering, drop the %ceffect%c.`;
338
342
  }
339
- return `State cascading detected. ${info.name} triggers ${targetName} in a separate frame. Merge them into one object to update simultaneously.`;
343
+ return `${info.name} updates, then ${targetName} updates on the next frame. If they are one fact, write them in the same %csetState%c.`;
340
344
  }
341
345
  if (duplicates.length > 0) {
342
346
  if (isBooleanLike(info.name)) {
343
- return `Boolean Explosion detected. Multiple flags are toggling in sync. Replace impossible states with a single %cstatus%c string ('idle' | 'loading' | 'success').`;
347
+ return `Several flags move together. One %cstatus%c value avoids impossible combinations.`;
344
348
  }
345
- return `Redundant State detected. This variable carries no unique information. Derive it from the source variable during render, or use %cuseMemo%c to cache the result.`;
349
+ return `These hooks move together. If one is just the other in another shape, compute it while %crendering%c.`;
346
350
  }
347
351
  if (issue.metric === "density") {
348
- return `High-Frequency Update. This variable updates faster than the frame rate. Apply %cdebounce%c or move to a Ref to unblock the main thread.`;
352
+ 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.`;
349
353
  }
350
- return `Check the dependency chain of ${info.name}.`;
354
+ return `Inspect ${info.name} and what updates with it.`;
351
355
  };
352
356
  var displayHealthReport = (history2, violationMap) => {
353
357
  if (!isWeb) return;
354
358
  const entries = Array.from(history2.entries());
355
359
  if (entries.length === 0) return;
356
360
  const topIssues = identifyTopIssues(instance.graph, history2, instance.redundantLabels, violationMap);
357
- console.group(`%c \u{1F4CA} BASIS | ARCHITECTURAL HEALTH REPORT `, STYLES.headerIdentity);
361
+ console.group(`%c BASIS | report `, STYLES.headerIdentity);
358
362
  if (topIssues.length > 0) {
359
- console.log(
360
- `%c\u{1F3AF} REFACTOR PRIORITIES %c(PRIME MOVERS)`,
361
- `font-weight: bold; color: ${THEME.identity}; margin-top: 10px;`,
362
- `font-weight: normal; color: ${THEME.muted}; font-style: italic;`
363
- );
363
+ console.log(`%cStart here`, `font-weight: bold; color: ${THEME.identity}; margin-top: 10px;`);
364
364
  topIssues.forEach((issue, idx) => {
365
365
  const info = parseLabel(issue.label);
366
- const icon = issue.metric === "influence" ? "\u26A1" : "\u{1F4C8}";
366
+ const icon = issue.metric === "influence" ? "\u2192" : "\u2022";
367
367
  const pColor = idx === 0 ? THEME.problem : idx === 1 ? THEME.solution : THEME.identity;
368
- let displayName = info.name;
369
- let displayFile = info.file;
370
- if (issue.label.includes("Global Event")) {
371
- displayName = info.name;
372
- displayFile = info.file;
373
- }
374
368
  console.group(
375
- ` %c${idx + 1}%c ${icon} ${displayName} %c(${displayFile})`,
369
+ ` %c${idx + 1}%c ${icon} ${displayName(issue.label)} %c(${info.file})`,
376
370
  `background: ${pColor}; color: ${idx === 1 ? "black" : "white"}; border-radius: 50%; padding: 0 5px;`,
377
371
  "font-family: monospace; font-weight: 700;",
378
- `color: ${THEME.muted}; font-size: 10px; font-weight: normal; font-style: italic;`
372
+ `color: ${THEME.muted}; font-size: 10px; font-weight: normal;`
379
373
  );
380
- console.log(`%c${issue.reason}`, `color: ${THEME.muted}; font-style: italic;`);
374
+ console.log(`%c${issue.reason}`, `color: ${THEME.muted};`);
381
375
  if (issue.violations.length > 0) {
382
376
  const byFile = /* @__PURE__ */ new Map();
383
377
  issue.violations.forEach((v) => {
384
378
  if (issue.label.includes("Global Event") && v.type === "context_mirror") return;
385
379
  const { file, name } = parseLabel(v.target);
386
380
  if (!byFile.has(file)) byFile.set(file, []);
387
- byFile.get(file).push(name);
381
+ byFile.get(file).push(name.replace(/:\d+$/, ""));
388
382
  });
389
383
  const impactParts = [];
390
384
  byFile.forEach((vars, file) => {
391
- const varList = vars.join(", ");
392
- impactParts.push(`${file} (${varList})`);
385
+ impactParts.push(`${file} (${vars.join(", ")})`);
393
386
  });
394
387
  if (impactParts.length > 0) {
395
- console.log(`%cImpacts: %c${impactParts.join(" + ")}`, STYLES.impactLabel, "");
388
+ console.log(`%cAlso updates: %c${impactParts.join(" \xB7 ")}`, STYLES.impactLabel, "");
396
389
  }
397
390
  }
398
391
  const fix = getSuggestedFix(issue, info);
399
392
  const fixParts = fix.split("%c");
400
393
  if (fixParts.length === 3) {
401
394
  console.log(
402
- `%cSolution: %c${fixParts[0]}%c${fixParts[1]}%c${fixParts[2]}`,
395
+ `%cTry: %c${fixParts[0]}%c${fixParts[1]}%c${fixParts[2]}`,
403
396
  STYLES.actionLabel,
404
397
  "",
405
398
  STYLES.actionPill,
406
399
  ""
407
400
  );
408
401
  } else {
409
- console.log(
410
- `%cSolution: %c${fix}`,
411
- STYLES.actionLabel,
412
- ""
413
- );
402
+ console.log(`%cTry: %c${fix}`, STYLES.actionLabel, "");
414
403
  }
415
404
  console.groupEnd();
416
405
  });
@@ -434,135 +423,170 @@ var displayHealthReport = (history2, violationMap) => {
434
423
  else independentCount++;
435
424
  });
436
425
  const totalVars = entries.length;
437
- const redundancyScore = (independentCount + clusters.length) / totalVars * 100;
438
- let internalEdges = 0;
439
- instance.graph.forEach((targets, source) => {
440
- if (source.startsWith("Event_Tick_")) return;
441
- internalEdges += targets.size;
442
- });
443
- const causalPenalty = internalEdges / totalVars * 100;
444
- let healthScore = redundancyScore - causalPenalty;
445
- if (healthScore < 0) healthScore = 0;
446
- const scoreColor = healthScore > 85 ? THEME.success : THEME.problem;
447
426
  console.log(
448
- `%cSystem Efficiency: %c${healthScore.toFixed(1)}%`,
449
- STYLES.bold,
450
- `color: ${scoreColor}; font-weight: bold;`
427
+ `%c${independentCount + clusters.length} of ${totalVars} instrumented hooks look independent in this window.`,
428
+ STYLES.subText
451
429
  );
452
- console.log(`%cSources of Truth: ${independentCount + clusters.length}/${totalVars} | Causal Leaks: ${internalEdges}`, STYLES.subText);
453
430
  if (clusters.length > 0) {
454
- console.log(`%cDetected ${clusters.length} Sync Issues:`, `font-weight: bold; color: ${THEME.problem}; margin-top: 10px;`);
431
+ console.log(
432
+ `%c${clusters.length} group${clusters.length === 1 ? "" : "s"} that keep updating together:`,
433
+ `font-weight: bold; color: ${THEME.problem}; margin-top: 10px;`
434
+ );
455
435
  clusters.forEach((cluster, idx) => {
456
436
  const clusterMetas = cluster.map((l) => ({
457
437
  label: l,
458
438
  meta: history2.get(l),
459
- name: parseLabel(l).name
439
+ name: displayName(l)
460
440
  }));
461
441
  const hasCtx = clusterMetas.some(
462
442
  (c) => c.meta.role === "context" /* CONTEXT */ || c.meta.role === "store" /* STORE */
463
443
  );
464
- const names = clusterMetas.map((c) => {
465
- const prefix = c.meta.role === "store" /* STORE */ ? "\u03A3 " : c.meta.role === "context" /* CONTEXT */ ? "\u03A9 " : "";
466
- return `${prefix}${c.name}`;
467
- }).join(" \u27F7 ");
468
- console.group(` %c${idx + 1}%c ${names}`, `background: ${THEME.problem}; color: white; border-radius: 50%; padding: 0 5px;`, "font-family: monospace; font-weight: bold;");
444
+ const names = clusterMetas.map((c) => c.name).join(", ");
445
+ console.group(
446
+ ` %c${idx + 1}%c ${names}`,
447
+ `background: ${THEME.problem}; color: white; border-radius: 50%; padding: 0 5px;`,
448
+ "font-family: monospace; font-weight: bold;"
449
+ );
469
450
  if (hasCtx) {
470
451
  const hasStore = clusterMetas.some((c) => c.meta.role === "store" /* STORE */);
471
- const sourceType = hasStore ? "External Store" : "global context";
472
- console.log(`%cDiagnosis: ${hasStore ? "Store" : "Context"} Mirroring. Local state is shadowing ${sourceType}.`, `color: ${THEME.problem};`);
473
- console.log(`%cSolution: Use ${sourceType} directly to avoid state drift.`, STYLES.actionLabel);
452
+ const sourceType = hasStore ? "a store" : "context";
453
+ console.log(`A local hook is only following ${sourceType}.`);
454
+ console.log(
455
+ `%cTry:%c Read ${sourceType} in render if the local value is not a draft.`,
456
+ STYLES.actionLabel,
457
+ ""
458
+ );
474
459
  } else {
475
- const boolKeywords = ["is", "has", "can", "should", "loading", "success", "error", "active", "enabled", "open", "visible"];
460
+ const boolKeywords = [
461
+ "is",
462
+ "has",
463
+ "can",
464
+ "should",
465
+ "loading",
466
+ "success",
467
+ "error",
468
+ "active",
469
+ "enabled",
470
+ "open",
471
+ "visible"
472
+ ];
476
473
  const boolCount = clusterMetas.filter(
477
474
  (c) => boolKeywords.some((kw) => c.name.toLowerCase().startsWith(kw))
478
475
  ).length;
479
- const isBoolExplosion = cluster.length > 2 && boolCount / cluster.length > 0.5;
480
- if (isBoolExplosion) {
481
- console.log(`%cDiagnosis:%c Boolean Explosion. Multiple booleans updating in sync.`, STYLES.bold, "");
482
- console.log(`%cSolution:%c Combine into a single %cstatus%c string or a %creducer%c.`, STYLES.actionLabel, "", STYLES.actionPill, "", STYLES.actionPill, "");
476
+ if (cluster.length > 2 && boolCount / cluster.length > 0.5) {
477
+ console.log(`These flags move together.`);
478
+ console.log(
479
+ `%cTry:%c One %cstatus%c instead of several booleans.`,
480
+ STYLES.actionLabel,
481
+ "",
482
+ STYLES.actionPill,
483
+ ""
484
+ );
483
485
  } else if (cluster.length > 2) {
484
- console.log(`%cDiagnosis:%c Sibling Updates. These states respond to the same event.`, STYLES.bold, "");
485
- console.log(`%cSolution:%c This may be intentional. If not, consolidate into a %creducer%c.`, STYLES.actionLabel, "", STYLES.actionPill, "");
486
+ console.log(`These hooks move on the same frames. Often the same click or fetch.`);
487
+ console.log(
488
+ `%cTry:%c Leave it if that is intentional. Otherwise one %creducer%c.`,
489
+ STYLES.actionLabel,
490
+ "",
491
+ STYLES.actionPill,
492
+ ""
493
+ );
486
494
  } else {
487
- console.log(`%cDiagnosis:%c Redundant State. Variables always change together.`, STYLES.bold, "");
488
- console.log(`%cSolution:%c Derive one from the other via %cuseMemo%c.`, STYLES.actionLabel, "", STYLES.actionPill, "");
495
+ console.log(`These two hooks keep updating in the same frame.`);
496
+ console.log(
497
+ `%cTry:%c If one is derived, compute it while %crendering%c.`,
498
+ STYLES.actionLabel,
499
+ "",
500
+ STYLES.actionPill,
501
+ ""
502
+ );
489
503
  }
490
504
  }
491
505
  console.groupEnd();
492
506
  });
493
507
  } else {
494
- console.log("%c\u2728 Your architecture is clean. No redundant state detected.", `color: ${THEME.success}; font-weight: bold;`);
508
+ console.log(
509
+ "%cNo hooks were updating in lockstep in this window.",
510
+ `color: ${THEME.success}; font-weight: bold;`
511
+ );
495
512
  }
496
513
  console.groupEnd();
497
514
  };
498
- var displayRedundancyAlert = (labelA, metaA, labelB, metaB, sim) => {
515
+ var displayRedundancyAlert = (labelA, metaA, labelB, metaB, overlap) => {
499
516
  if (!isWeb || !shouldLog(`redundant-${labelA}-${labelB}`)) return;
500
517
  const infoA = parseLabel(labelA);
501
- const infoB = parseLabel(labelB);
518
+ const nameA = displayName(labelA);
519
+ const nameB = displayName(labelB);
502
520
  const isContextMirror = metaA.role === "local" /* LOCAL */ && metaB.role === "context" /* CONTEXT */ || metaB.role === "local" /* LOCAL */ && metaA.role === "context" /* CONTEXT */;
503
521
  const isStoreMirror = metaA.role === "local" /* LOCAL */ && metaB.role === "store" /* STORE */ || metaB.role === "local" /* LOCAL */ && metaA.role === "store" /* STORE */;
504
- const alertType = isContextMirror ? "CONTEXT MIRRORING" : isStoreMirror ? "STORE MIRRORING" : "DUPLICATE STATE";
505
- console.group(`%c \u264A BASIS | ${alertType} `, STYLES.headerProblem);
506
- console.log(`%c\u{1F4CD} Location: %c${infoA.file}`, STYLES.bold, STYLES.location);
507
- console.log(`%cIssue:%c ${infoA.name} and ${infoB.name} overlapped on ${(sim * 100).toFixed(0)}% of aligned updates.`, STYLES.bold, "");
522
+ const alertType = isContextMirror ? "local state follows context" : isStoreMirror ? "local state follows a store" : "hooks moving together";
523
+ const times = overlap.kSync === 1 ? "time" : "times";
524
+ console.group(`%c BASIS | ${alertType} `, STYLES.headerProblem);
525
+ console.log(`%c${infoA.file}`, STYLES.location);
526
+ console.log(
527
+ `%c${nameA}%c and %c${nameB}%c updated in the same frame ${overlap.kSync} ${times}.`,
528
+ STYLES.label,
529
+ "",
530
+ STYLES.label,
531
+ ""
532
+ );
508
533
  if (isContextMirror || isStoreMirror) {
509
- const sourceType = isStoreMirror ? "External Store" : "Global Context";
534
+ const sourceType = isStoreMirror ? "store" : "context";
510
535
  console.log(
511
- `%cFix:%c Local state is 'shadowing' ${sourceType}. Delete the local state and consume the %c${sourceType}%c value directly.`,
536
+ `%cTry:%c If this is not a draft, delete the local hook and read the %c${sourceType}%c in render.`,
537
+ STYLES.bold,
538
+ "",
539
+ STYLES.actionPill,
540
+ ""
541
+ );
542
+ } else if (isBooleanLike(nameA) || isBooleanLike(nameB)) {
543
+ console.log(
544
+ `%cTry:%c One %cstatus%c instead of several flags.`,
512
545
  STYLES.bold,
513
546
  "",
514
547
  STYLES.actionPill,
515
548
  ""
516
549
  );
517
550
  } else {
518
- if (isBooleanLike(infoA.name) || isBooleanLike(infoB.name)) {
519
- console.log(
520
- `%cFix:%c Boolean Explosion detected. Merge flags into a single %cstatus%c string or %cuseReducer%c.`,
521
- STYLES.bold,
522
- "",
523
- STYLES.actionPill,
524
- "",
525
- STYLES.actionPill,
526
- ""
527
- );
528
- } else {
529
- console.log(
530
- `%cFix:%c Redundant State detected. Derive %c${infoB.name}%c from %c${infoA.name}%c during render, or use %cuseMemo%c.`,
531
- STYLES.bold,
532
- "",
533
- STYLES.label,
534
- "",
535
- STYLES.label,
536
- "",
537
- STYLES.actionPill,
538
- ""
539
- );
540
- }
551
+ console.log(
552
+ `%cTry:%c If %c${nameB}%c is just %c${nameA}%c in another shape, compute it while rendering.`,
553
+ STYLES.bold,
554
+ "",
555
+ STYLES.label,
556
+ "",
557
+ STYLES.label,
558
+ ""
559
+ );
541
560
  }
542
561
  console.groupEnd();
543
562
  };
544
- var displayCausalHint = (targetLabel, targetMeta, sourceLabel, sourceMeta) => {
563
+ var displayCausalHint = (targetLabel, _targetMeta, sourceLabel, sourceMeta) => {
545
564
  if (!isWeb || !shouldLog(`causal-${sourceLabel}-${targetLabel}`)) return;
546
565
  const target = parseLabel(targetLabel);
547
- const source = parseLabel(sourceLabel);
548
- const headerType = sourceMeta.role === "context" /* CONTEXT */ ? "CONTEXT SYNC LEAK" : sourceMeta.role === "store" /* STORE */ ? "STORE SYNC LEAK" : "DOUBLE RENDER";
566
+ const sourceName = displayName(sourceLabel);
567
+ const targetName = displayName(targetLabel);
568
+ const headerType = sourceMeta.role === "context" /* CONTEXT */ ? "extra render from context" : sourceMeta.role === "store" /* STORE */ ? "extra render from a store" : "extra render";
549
569
  const isEffect = sourceLabel.includes("effect") || sourceLabel.includes("useLayoutEffect");
550
- console.groupCollapsed(`%c \u26A1 BASIS | ${headerType} `, STYLES.headerProblem);
551
- console.log(`%c\u{1F4CD} Location: %c${target.file}`, STYLES.bold, STYLES.location);
552
- console.log(`%cIssue:%c ${source.name} triggers ${target.name} in separate frames.`, STYLES.bold, "");
570
+ console.groupCollapsed(`%c BASIS | ${headerType} `, STYLES.headerProblem);
571
+ console.log(`%c${target.file}`, STYLES.location);
572
+ console.log(
573
+ `%c${sourceName}%c updates %c${targetName}%c on the next frame.`,
574
+ STYLES.label,
575
+ "",
576
+ STYLES.label,
577
+ ""
578
+ );
553
579
  if (isEffect) {
554
580
  console.log(
555
- `%cFix:%c Derive %c${target.name}%c during the render phase (remove effect) or wrap in %cuseMemo%c.`,
581
+ `%cTry:%c If %c${targetName}%c can be computed while rendering, drop the extra setState.`,
556
582
  STYLES.bold,
557
583
  "",
558
584
  STYLES.label,
559
- "",
560
- STYLES.actionPill,
561
585
  ""
562
586
  );
563
587
  } else {
564
588
  console.log(
565
- `%cFix:%c Merge %c${target.name}%c with %c${source.name}%c into a single state update.`,
589
+ `%cTry:%c Write %c${targetName}%c in the same update as %c${sourceName}%c if they are one fact.`,
566
590
  STYLES.bold,
567
591
  "",
568
592
  STYLES.label,
@@ -595,7 +619,7 @@ var displayGraphReport = (graph) => {
595
619
  if (!isWeb) return;
596
620
  if (graph.nodes.length === 0) {
597
621
  console.log(
598
- `%c \u{1F4CA} BASIS | CAUSAL GRAPH %c(no data yet)`,
622
+ `%c BASIS | update graph %c(nothing recorded yet)`,
599
623
  STYLES.headerIdentity,
600
624
  `color: ${THEME.muted}; font-style: italic;`
601
625
  );
@@ -614,17 +638,22 @@ var displayGraphReport = (graph) => {
614
638
  occurrences: g.occurrences
615
639
  }));
616
640
  const groupedSourceIds = new Set(graph.eventGroups.flatMap((g) => g.sourceIds));
617
- const nonEventGroups = Array.from(outgoing.keys()).filter((id) => !groupedSourceIds.has(id)).map((id) => ({ sourceIds: [id], sourceNode: nodeById.get(id), edges: outgoing.get(id), occurrences: 1 }));
641
+ const nonEventGroups = Array.from(outgoing.keys()).filter((id) => !groupedSourceIds.has(id)).map((id) => ({
642
+ sourceIds: [id],
643
+ sourceNode: nodeById.get(id),
644
+ edges: outgoing.get(id),
645
+ occurrences: 1
646
+ }));
618
647
  const groups = [...eventGroups, ...nonEventGroups].sort(
619
648
  (a, b) => b.edges.length - a.edges.length || b.occurrences - a.occurrences
620
649
  );
621
650
  console.group(
622
- `%c \u{1F4CA} BASIS | CAUSAL GRAPH %c${graph.nodes.length} nodes \xB7 ${graph.edges.length} edges \xB7 ${groups.length} sources \xB7 buffer window ${graph.bufferWindowSize}`,
651
+ `%c BASIS | update graph %c${graph.nodes.length} nodes \xB7 ${graph.edges.length} edges \xB7 ${groups.length} sources \xB7 last ${graph.bufferWindowSize} frames`,
623
652
  STYLES.headerIdentity,
624
- `color: ${THEME.muted}; font-weight: normal; font-style: italic;`
653
+ `color: ${THEME.muted}; font-weight: normal;`
625
654
  );
626
655
  console.log(
627
- `%cparent \u2192 child = observed cause \u2192 update. (\xD7N) = times in this window. Event groups with the same fan-out are collapsed.`,
656
+ `%cparent \u2192 child = what we saw cause an update. (\xD7N) = times in this window. Repeat clicks with the same targets are grouped.`,
628
657
  STYLES.subText
629
658
  );
630
659
  groups.forEach((group) => {
@@ -632,12 +661,12 @@ var displayGraphReport = (graph) => {
632
661
  const isCtx = group.sourceNode?.role === "context" /* CONTEXT */;
633
662
  const isFx = group.sourceNode?.role === "effect";
634
663
  const isUnknown = group.sourceNode?.role === "unknown";
635
- const icon = isEvent ? "\u26A1" : isCtx ? "\u03A9" : isFx ? "\u21AF" : isUnknown ? "?" : "\u25CF";
664
+ const icon = isEvent ? "\u2022" : isCtx ? "ctx" : isFx ? "fx" : isUnknown ? "?" : "\u2022";
636
665
  const color = isEvent ? THEME.solution : isCtx ? THEME.context : THEME.identity;
637
666
  const fanout = group.edges.length;
638
667
  const hits = group.occurrences;
639
668
  const hitLabel = hits > 1 ? ` \xB7 \xD7${hits}` : "";
640
- const title = isEvent ? `Event \xB7 ${fanout} target${fanout === 1 ? "" : "s"}${hitLabel}` : formatNode(group.sourceNode, group.sourceIds[0]);
669
+ const title = isEvent ? `click / event \xB7 ${fanout} update${fanout === 1 ? "" : "s"}${hitLabel}` : formatNode(group.sourceNode, group.sourceIds[0]);
641
670
  console.groupCollapsed(
642
671
  `%c${icon} %c${title}`,
643
672
  `color: ${color};`,
@@ -649,16 +678,16 @@ var displayGraphReport = (graph) => {
649
678
  const weight = edge.weight > 1 ? ` (\xD7${edge.weight})` : "";
650
679
  if (target?.redundant) {
651
680
  console.log(
652
- `%c ${label}%c${weight} %credundant`,
681
+ `%c ${label}%c${weight} %cmoving with another hook`,
653
682
  `color: ${THEME.muted}; font-family: monospace;`,
654
- `color: ${THEME.muted}; font-style: italic;`,
683
+ `color: ${THEME.muted};`,
655
684
  `color: ${THEME.problem}; font-weight: bold;`
656
685
  );
657
686
  } else {
658
687
  console.log(
659
688
  `%c ${label}%c${weight}`,
660
689
  `color: ${THEME.muted}; font-family: monospace;`,
661
- `color: ${THEME.muted}; font-style: italic;`
690
+ `color: ${THEME.muted};`
662
691
  );
663
692
  }
664
693
  });
@@ -666,14 +695,17 @@ var displayGraphReport = (graph) => {
666
695
  });
667
696
  console.groupEnd();
668
697
  };
669
- var displayViolentBreaker = (label, count, threshold) => {
698
+ var displayViolentBreaker = (label, count, _threshold) => {
670
699
  if (!isWeb) return;
671
- const { name } = parseLabel(label);
672
- console.group(`%c \u{1F6D1} BASIS CRITICAL | CIRCUIT BREAKER `, STYLES.headerProblem);
673
- console.error(`INFINITE LOOP DETECTED
674
- Variable: ${name}
675
- Frequency: ${count} updates/sec`);
676
- console.log(`%cACTION: Update BLOCKED to prevent browser freeze.`, `color: ${THEME.problem}; font-weight: bold;`);
700
+ const name = displayName(label);
701
+ console.group(`%c BASIS | loop guard `, STYLES.headerProblem);
702
+ console.error(
703
+ `${name} updated ${count} times in one second. Basis stopped recording this path so the tab stays usable.`
704
+ );
705
+ console.log(
706
+ `%cReact may still error on its own. Fix the effect that writes a value it also lists as a dependency.`,
707
+ `color: ${THEME.muted};`
708
+ );
677
709
  console.groupEnd();
678
710
  };
679
711
 
@@ -715,6 +747,12 @@ var calculateAllSimilarities = (entryA, entryB) => {
715
747
  significantLead
716
748
  };
717
749
  };
750
+ var overlapFrom = (s) => ({
751
+ kSync: s.kSync,
752
+ densityA: s.densityA,
753
+ densityB: s.densityB,
754
+ cosine: s.sync
755
+ });
718
756
  var shouldSkipComparison = (entryA, entryB, dirtyLabels2) => {
719
757
  if (entryA.label === entryB.label) return true;
720
758
  if (isSameField(entryA.label, entryB.label)) return true;
@@ -739,21 +777,21 @@ var detectRedundancy = (entryA, entryB, similarities, redundantSet, violationMap
739
777
  const roleB = entryB.meta.role;
740
778
  if (isGlobalSource(roleA) && isGlobalSource(roleB)) return;
741
779
  if (similarities.densityA < 2 || similarities.densityB < 2) return;
742
- const score = similarities.sync;
780
+ const overlap = overlapFrom(similarities);
743
781
  if (roleA === "local" /* LOCAL */ && isGlobalSource(roleB)) {
744
782
  redundantSet.add(entryA.label);
745
- pushViolation(violationMap, entryB.label, { type: "context_mirror", target: entryA.label, similarity: score });
746
- displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta, score);
783
+ pushViolation(violationMap, entryB.label, { type: "context_mirror", target: entryA.label, overlap });
784
+ displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta, overlap);
747
785
  } else if (isGlobalSource(roleA) && roleB === "local" /* LOCAL */) {
748
786
  redundantSet.add(entryB.label);
749
- pushViolation(violationMap, entryA.label, { type: "context_mirror", target: entryB.label, similarity: score });
750
- displayRedundancyAlert(entryB.label, entryB.meta, entryA.label, entryA.meta, score);
787
+ pushViolation(violationMap, entryA.label, { type: "context_mirror", target: entryB.label, overlap });
788
+ displayRedundancyAlert(entryB.label, entryB.meta, entryA.label, entryA.meta, overlap);
751
789
  } else if (roleA === "local" /* LOCAL */ && roleB === "local" /* LOCAL */) {
752
790
  redundantSet.add(entryA.label);
753
791
  redundantSet.add(entryB.label);
754
- pushViolation(violationMap, entryA.label, { type: "duplicate_state", target: entryB.label, similarity: score });
755
- pushViolation(violationMap, entryB.label, { type: "duplicate_state", target: entryA.label, similarity: score });
756
- displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta, score);
792
+ pushViolation(violationMap, entryA.label, { type: "duplicate_state", target: entryB.label, overlap });
793
+ pushViolation(violationMap, entryB.label, { type: "duplicate_state", target: entryA.label, overlap });
794
+ displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta, overlap);
757
795
  }
758
796
  };
759
797
  var detectCausalLeak = (entryA, entryB, similarities, violationMap, graph) => {