react-state-basis 0.6.4 → 0.6.5

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
@@ -34,7 +34,9 @@ __export(index_exports, {
34
34
  basis: () => basis,
35
35
  configureBasis: () => configureBasis,
36
36
  createContext: () => createContext,
37
+ getBasisGraph: () => getBasisGraph,
37
38
  getBasisMetrics: () => getBasisMetrics,
39
+ printBasisGraph: () => printBasisGraph,
38
40
  printBasisHealthReport: () => printBasisHealthReport,
39
41
  use: () => use,
40
42
  useActionState: () => useActionState,
@@ -125,6 +127,27 @@ var calculateSpectralInfluence = (graph, maxIterations = 20, tolerance = 1e-3) =
125
127
  }
126
128
  return scores;
127
129
  };
130
+ var groupEventSources = (nodes, edges) => {
131
+ const outgoing = /* @__PURE__ */ new Map();
132
+ edges.forEach((e) => {
133
+ if (!outgoing.has(e.source)) outgoing.set(e.source, []);
134
+ outgoing.get(e.source).push(e);
135
+ });
136
+ const eventSourceIds = nodes.filter((n) => n.role === "event").map((n) => n.id);
137
+ const signatureOf = (sourceEdges) => sourceEdges.map((e) => `${e.target}@${e.weight}`).sort().join("|");
138
+ const buckets = /* @__PURE__ */ new Map();
139
+ eventSourceIds.forEach((id) => {
140
+ const sig = signatureOf(outgoing.get(id) || []);
141
+ if (!buckets.has(sig)) buckets.set(sig, []);
142
+ buckets.get(sig).push(id);
143
+ });
144
+ const groups = Array.from(buckets.values()).map((sourceIds) => ({
145
+ sourceIds,
146
+ occurrences: sourceIds.length,
147
+ edges: (outgoing.get(sourceIds[0]) || []).slice()
148
+ }));
149
+ return groups.sort((a, b) => b.edges.length - a.edges.length || b.occurrences - a.occurrences);
150
+ };
128
151
 
129
152
  // src/core/constants.ts
130
153
  var WINDOW_SIZE = 50;
@@ -144,6 +167,7 @@ var parseLabel = (label) => {
144
167
  return { file: parts[0] || "Unknown", name: parts[1] || base };
145
168
  };
146
169
  var isSameField = (labelA, labelB) => stripInstance(labelA) === stripInstance(labelB);
170
+ var isEffectLabel = (name) => /^effect_L\d+(:\d+)?$/.test(name) || name === "anonymous_effect" || name === "anonymous_layout_effect";
147
171
 
148
172
  // src/core/ranker.ts
149
173
  var identifyTopIssues = (graph, history2, redundantLabels2, violationMap) => {
@@ -539,6 +563,99 @@ var displayCausalHint = (targetLabel, targetMeta, sourceLabel, sourceMeta) => {
539
563
  }
540
564
  console.groupEnd();
541
565
  };
566
+ var splitHookLine = (raw) => {
567
+ const m = raw.match(/^(.*):(\d+)$/);
568
+ if (!m) return { hook: raw };
569
+ return { hook: m[1], line: Number(m[2]) };
570
+ };
571
+ var formatHook = (raw) => {
572
+ const { hook } = splitHookLine(raw);
573
+ if (!isEffectLabel(hook)) return hook;
574
+ const lineMatch = hook.match(/L(\d+)$/);
575
+ return lineMatch ? `effect @ L${lineMatch[1]}` : "effect (anonymous)";
576
+ };
577
+ var formatNode = (node, fallbackId = "?") => {
578
+ if (!node) return fallbackId;
579
+ if (node.role === "event") return "Event";
580
+ const hook = formatHook(node.name || node.id);
581
+ if (node.file && hook) return `${node.file} \u2192 ${hook}`;
582
+ return hook || node.id;
583
+ };
584
+ var displayGraphReport = (graph) => {
585
+ if (!isWeb) return;
586
+ if (graph.nodes.length === 0) {
587
+ console.log(
588
+ `%c \u{1F4CA} BASIS | CAUSAL GRAPH %c(no data yet)`,
589
+ STYLES.headerIdentity,
590
+ `color: ${THEME.muted}; font-style: italic;`
591
+ );
592
+ return;
593
+ }
594
+ const nodeById = new Map(graph.nodes.map((n) => [n.id, n]));
595
+ const outgoing = /* @__PURE__ */ new Map();
596
+ graph.edges.forEach((e) => {
597
+ if (!outgoing.has(e.source)) outgoing.set(e.source, []);
598
+ outgoing.get(e.source).push(e);
599
+ });
600
+ const eventGroups = graph.eventGroups.map((g) => ({
601
+ sourceIds: g.sourceIds,
602
+ sourceNode: nodeById.get(g.sourceIds[0]),
603
+ edges: g.edges,
604
+ occurrences: g.occurrences
605
+ }));
606
+ const groupedSourceIds = new Set(graph.eventGroups.flatMap((g) => g.sourceIds));
607
+ 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 }));
608
+ const groups = [...eventGroups, ...nonEventGroups].sort(
609
+ (a, b) => b.edges.length - a.edges.length || b.occurrences - a.occurrences
610
+ );
611
+ console.group(
612
+ `%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}`,
613
+ STYLES.headerIdentity,
614
+ `color: ${THEME.muted}; font-weight: normal; font-style: italic;`
615
+ );
616
+ console.log(
617
+ `%cparent \u2192 child = observed cause \u2192 update. (\xD7N) = times in this window. Event groups with the same fan-out are collapsed.`,
618
+ STYLES.subText
619
+ );
620
+ groups.forEach((group) => {
621
+ const isEvent = group.sourceNode?.role === "event";
622
+ const isCtx = group.sourceNode?.role === "context" /* CONTEXT */;
623
+ const isFx = group.sourceNode?.role === "effect";
624
+ const isUnknown = group.sourceNode?.role === "unknown";
625
+ const icon = isEvent ? "\u26A1" : isCtx ? "\u03A9" : isFx ? "\u21AF" : isUnknown ? "?" : "\u25CF";
626
+ const color = isEvent ? THEME.solution : isCtx ? THEME.context : THEME.identity;
627
+ const fanout = group.edges.length;
628
+ const hits = group.occurrences;
629
+ const hitLabel = hits > 1 ? ` \xB7 \xD7${hits}` : "";
630
+ const title = isEvent ? `Event \xB7 ${fanout} target${fanout === 1 ? "" : "s"}${hitLabel}` : formatNode(group.sourceNode, group.sourceIds[0]);
631
+ console.groupCollapsed(
632
+ `%c${icon} %c${title}`,
633
+ `color: ${color};`,
634
+ "font-family: monospace; font-weight: 600;"
635
+ );
636
+ group.edges.slice().sort((a, b) => b.weight - a.weight).forEach((edge) => {
637
+ const target = nodeById.get(edge.target);
638
+ const label = formatNode(target, edge.target);
639
+ const weight = edge.weight > 1 ? ` (\xD7${edge.weight})` : "";
640
+ if (target?.redundant) {
641
+ console.log(
642
+ `%c ${label}%c${weight} %credundant`,
643
+ `color: ${THEME.muted}; font-family: monospace;`,
644
+ `color: ${THEME.muted}; font-style: italic;`,
645
+ `color: ${THEME.problem}; font-weight: bold;`
646
+ );
647
+ } else {
648
+ console.log(
649
+ `%c ${label}%c${weight}`,
650
+ `color: ${THEME.muted}; font-family: monospace;`,
651
+ `color: ${THEME.muted}; font-style: italic;`
652
+ );
653
+ }
654
+ });
655
+ console.groupEnd();
656
+ });
657
+ console.groupEnd();
658
+ };
542
659
  var displayViolentBreaker = (label, count, threshold) => {
543
660
  if (!isWeb) return;
544
661
  const { name } = parseLabel(label);
@@ -925,9 +1042,53 @@ var getBasisMetrics = () => ({
925
1042
  analysis_ms: instance.metrics.lastAnalysisTimeMs.toFixed(3),
926
1043
  entropy: instance.metrics.systemEntropy.toFixed(3)
927
1044
  });
1045
+ var getBasisGraph = () => {
1046
+ const nodeIds = /* @__PURE__ */ new Set();
1047
+ const edges = [];
1048
+ instance.graph.forEach((targets, source) => {
1049
+ nodeIds.add(source);
1050
+ targets.forEach((weight, target) => {
1051
+ nodeIds.add(target);
1052
+ edges.push({ source, target, weight });
1053
+ });
1054
+ });
1055
+ const nodes = Array.from(nodeIds).map((id) => {
1056
+ if (id.startsWith("Event_Tick_")) {
1057
+ return { id, name: "Event", file: "(shared trigger)", role: "event", density: null, redundant: false };
1058
+ }
1059
+ const meta = instance.history.get(id);
1060
+ const { file, name } = parseLabel(id);
1061
+ if (!meta) {
1062
+ const role = isEffectLabel(name) ? "effect" : "unknown";
1063
+ return { id, name, file, role, density: null, redundant: false };
1064
+ }
1065
+ return {
1066
+ id,
1067
+ name,
1068
+ file,
1069
+ role: meta.role,
1070
+ density: meta.density,
1071
+ redundant: instance.redundantLabels.has(id)
1072
+ };
1073
+ });
1074
+ return {
1075
+ generatedAt: Date.now(),
1076
+ bufferWindowSize: WINDOW_SIZE,
1077
+ eventTtlMs: EVENT_TTL,
1078
+ nodes,
1079
+ edges,
1080
+ eventGroups: groupEventSources(nodes, edges)
1081
+ };
1082
+ };
1083
+ var printBasisGraph = () => {
1084
+ if (!instance.config.debug) return;
1085
+ displayGraphReport(getBasisGraph());
1086
+ };
928
1087
  if (typeof window !== "undefined") {
929
1088
  window.printBasisReport = printBasisHealthReport;
930
1089
  window.getBasisMetrics = getBasisMetrics;
1090
+ window.getBasisGraph = getBasisGraph;
1091
+ window.printBasisGraph = printBasisGraph;
931
1092
  }
932
1093
 
933
1094
  // src/hooks.ts
@@ -1330,7 +1491,9 @@ function basis() {
1330
1491
  basis,
1331
1492
  configureBasis,
1332
1493
  createContext,
1494
+ getBasisGraph,
1333
1495
  getBasisMetrics,
1496
+ printBasisGraph,
1334
1497
  printBasisHealthReport,
1335
1498
  use,
1336
1499
  useActionState,