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/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 `These variables update together but live in different hooks/files. Consolidate them into a single %cuseReducer%c or atomic store update.`;
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 `Local state is 'shadowing' Global Context. This creates two sources of truth. Delete the local state and consume the %cContext%c value directly.`;
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 = parseLabel(leaks[0].target).name;
379
+ const targetName = displayName(leaks[0].target);
376
380
  if (issue.label.includes("effect")) {
377
- 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.`;
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 `State cascading detected. ${info.name} triggers ${targetName} in a separate frame. Merge them into one object to update simultaneously.`;
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 `Boolean Explosion detected. Multiple flags are toggling in sync. Replace impossible states with a single %cstatus%c string ('idle' | 'loading' | 'success').`;
387
+ return `Several flags move together. One %cstatus%c value avoids impossible combinations.`;
384
388
  }
385
- 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.`;
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 `High-Frequency Update. This variable updates faster than the frame rate. Apply %cdebounce%c or move to a Ref to unblock the main thread.`;
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 `Check the dependency chain of ${info.name}.`;
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 \u{1F4CA} BASIS | ARCHITECTURAL HEALTH REPORT `, STYLES.headerIdentity);
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" ? "\u26A1" : "\u{1F4C8}";
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(${displayFile})`,
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; font-style: italic;`
412
+ `color: ${THEME.muted}; font-size: 10px; font-weight: normal;`
419
413
  );
420
- console.log(`%c${issue.reason}`, `color: ${THEME.muted}; font-style: italic;`);
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
- const varList = vars.join(", ");
432
- impactParts.push(`${file} (${varList})`);
425
+ impactParts.push(`${file} (${vars.join(", ")})`);
433
426
  });
434
427
  if (impactParts.length > 0) {
435
- console.log(`%cImpacts: %c${impactParts.join(" + ")}`, STYLES.impactLabel, "");
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
- `%cSolution: %c${fixParts[0]}%c${fixParts[1]}%c${fixParts[2]}`,
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
- `%cSystem Efficiency: %c${healthScore.toFixed(1)}%`,
489
- STYLES.bold,
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(`%cDetected ${clusters.length} Sync Issues:`, `font-weight: bold; color: ${THEME.problem}; margin-top: 10px;`);
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: parseLabel(l).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
- const prefix = c.meta.role === "store" /* STORE */ ? "\u03A3 " : c.meta.role === "context" /* CONTEXT */ ? "\u03A9 " : "";
506
- return `${prefix}${c.name}`;
507
- }).join(" \u27F7 ");
508
- console.group(` %c${idx + 1}%c ${names}`, `background: ${THEME.problem}; color: white; border-radius: 50%; padding: 0 5px;`, "font-family: monospace; font-weight: bold;");
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 ? "External Store" : "global context";
512
- console.log(`%cDiagnosis: ${hasStore ? "Store" : "Context"} Mirroring. Local state is shadowing ${sourceType}.`, `color: ${THEME.problem};`);
513
- console.log(`%cSolution: Use ${sourceType} directly to avoid state drift.`, STYLES.actionLabel);
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 = ["is", "has", "can", "should", "loading", "success", "error", "active", "enabled", "open", "visible"];
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
- const isBoolExplosion = cluster.length > 2 && boolCount / cluster.length > 0.5;
520
- if (isBoolExplosion) {
521
- console.log(`%cDiagnosis:%c Boolean Explosion. Multiple booleans updating in sync.`, STYLES.bold, "");
522
- console.log(`%cSolution:%c Combine into a single %cstatus%c string or a %creducer%c.`, STYLES.actionLabel, "", STYLES.actionPill, "", STYLES.actionPill, "");
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(`%cDiagnosis:%c Sibling Updates. These states respond to the same event.`, STYLES.bold, "");
525
- console.log(`%cSolution:%c This may be intentional. If not, consolidate into a %creducer%c.`, STYLES.actionLabel, "", STYLES.actionPill, "");
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(`%cDiagnosis:%c Redundant State. Variables always change together.`, STYLES.bold, "");
528
- console.log(`%cSolution:%c Derive one from the other via %cuseMemo%c.`, STYLES.actionLabel, "", STYLES.actionPill, "");
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("%c\u2728 Your architecture is clean. No redundant state detected.", `color: ${THEME.success}; font-weight: bold;`);
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, sim) => {
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 infoB = parseLabel(labelB);
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 ? "CONTEXT MIRRORING" : isStoreMirror ? "STORE MIRRORING" : "DUPLICATE STATE";
545
- console.group(`%c \u264A BASIS | ${alertType} `, STYLES.headerProblem);
546
- console.log(`%c\u{1F4CD} Location: %c${infoA.file}`, STYLES.bold, STYLES.location);
547
- console.log(`%cIssue:%c ${infoA.name} and ${infoB.name} overlapped on ${(sim * 100).toFixed(0)}% of aligned updates.`, STYLES.bold, "");
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 ? "External Store" : "Global Context";
574
+ const sourceType = isStoreMirror ? "store" : "context";
550
575
  console.log(
551
- `%cFix:%c Local state is 'shadowing' ${sourceType}. Delete the local state and consume the %c${sourceType}%c value directly.`,
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
- if (isBooleanLike(infoA.name) || isBooleanLike(infoB.name)) {
559
- console.log(
560
- `%cFix:%c Boolean Explosion detected. Merge flags into a single %cstatus%c string or %cuseReducer%c.`,
561
- STYLES.bold,
562
- "",
563
- STYLES.actionPill,
564
- "",
565
- STYLES.actionPill,
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, targetMeta, sourceLabel, sourceMeta) => {
603
+ var displayCausalHint = (targetLabel, _targetMeta, sourceLabel, sourceMeta) => {
585
604
  if (!isWeb || !shouldLog(`causal-${sourceLabel}-${targetLabel}`)) return;
586
605
  const target = parseLabel(targetLabel);
587
- const source = parseLabel(sourceLabel);
588
- const headerType = sourceMeta.role === "context" /* CONTEXT */ ? "CONTEXT SYNC LEAK" : sourceMeta.role === "store" /* STORE */ ? "STORE SYNC LEAK" : "DOUBLE RENDER";
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 \u26A1 BASIS | ${headerType} `, STYLES.headerProblem);
591
- console.log(`%c\u{1F4CD} Location: %c${target.file}`, STYLES.bold, STYLES.location);
592
- console.log(`%cIssue:%c ${source.name} triggers ${target.name} in separate frames.`, STYLES.bold, "");
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
- `%cFix:%c Derive %c${target.name}%c during the render phase (remove effect) or wrap in %cuseMemo%c.`,
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
- `%cFix:%c Merge %c${target.name}%c with %c${source.name}%c into a single state update.`,
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 \u{1F4CA} BASIS | CAUSAL GRAPH %c(no data yet)`,
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) => ({ sourceIds: [id], sourceNode: nodeById.get(id), edges: outgoing.get(id), occurrences: 1 }));
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 \u{1F4CA} BASIS | CAUSAL GRAPH %c${graph.nodes.length} nodes \xB7 ${graph.edges.length} edges \xB7 ${groups.length} sources \xB7 buffer window ${graph.bufferWindowSize}`,
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; font-style: italic;`
693
+ `color: ${THEME.muted}; font-weight: normal;`
665
694
  );
666
695
  console.log(
667
- `%cparent \u2192 child = observed cause \u2192 update. (\xD7N) = times in this window. Event groups with the same fan-out are collapsed.`,
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 ? "\u26A1" : isCtx ? "\u03A9" : isFx ? "\u21AF" : isUnknown ? "?" : "\u25CF";
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 ? `Event \xB7 ${fanout} target${fanout === 1 ? "" : "s"}${hitLabel}` : formatNode(group.sourceNode, group.sourceIds[0]);
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} %credundant`,
721
+ `%c ${label}%c${weight} %cmoving with another hook`,
693
722
  `color: ${THEME.muted}; font-family: monospace;`,
694
- `color: ${THEME.muted}; font-style: italic;`,
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}; font-style: italic;`
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, threshold) => {
738
+ var displayViolentBreaker = (label, count, _threshold) => {
710
739
  if (!isWeb) return;
711
- const { name } = parseLabel(label);
712
- console.group(`%c \u{1F6D1} BASIS CRITICAL | CIRCUIT BREAKER `, STYLES.headerProblem);
713
- console.error(`INFINITE LOOP DETECTED
714
- Variable: ${name}
715
- Frequency: ${count} updates/sec`);
716
- console.log(`%cACTION: Update BLOCKED to prevent browser freeze.`, `color: ${THEME.problem}; font-weight: bold;`);
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(`%cBasis%cAuditor%c "Graph Era" (Window: ${windowSize})`, STYLES.basis, STYLES.version, `color: ${THEME.muted}; font-style: italic; margin-left: 8px;`);
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 score = similarities.sync;
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, similarity: score });
790
- displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta, score);
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, similarity: score });
794
- displayRedundancyAlert(entryB.label, entryB.meta, entryA.label, entryA.meta, score);
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, similarity: score });
799
- pushViolation(violationMap, entryB.label, { type: "duplicate_state", target: entryA.label, similarity: score });
800
- displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta, score);
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) => {