react-state-basis 0.5.0 → 0.6.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/README.md +35 -19
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +515 -120
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +515 -120
- package/dist/index.mjs.map +1 -1
- package/dist/production.d.mts +1 -0
- package/dist/production.d.ts +1 -0
- package/dist/production.js.map +1 -1
- package/dist/production.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -48,24 +48,176 @@ var calculateCosineSimilarity = (A, B) => {
|
|
|
48
48
|
return magA === 0 || magB === 0 ? 0 : dot / (Math.sqrt(magA) * Math.sqrt(magB));
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
+
// src/core/graph.ts
|
|
52
|
+
var calculateSpectralInfluence = (graph, maxIterations = 20, tolerance = 1e-3) => {
|
|
53
|
+
const nodes = Array.from(/* @__PURE__ */ new Set([...graph.keys(), ...Array.from(graph.values()).flatMap((m) => [...m.keys()])]));
|
|
54
|
+
if (nodes.length === 0) return /* @__PURE__ */ new Map();
|
|
55
|
+
let scores = /* @__PURE__ */ new Map();
|
|
56
|
+
nodes.forEach((n) => scores.set(n, 1 / nodes.length));
|
|
57
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
58
|
+
const nextScores = /* @__PURE__ */ new Map();
|
|
59
|
+
let totalWeight = 0;
|
|
60
|
+
nodes.forEach((source) => {
|
|
61
|
+
let influence = 0;
|
|
62
|
+
const outgoing = graph.get(source);
|
|
63
|
+
if (outgoing) {
|
|
64
|
+
outgoing.forEach((weight, target) => {
|
|
65
|
+
if (source !== target) {
|
|
66
|
+
influence += (scores.get(target) || 0) * weight;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
nextScores.set(source, influence + 0.01);
|
|
71
|
+
totalWeight += influence + 0.01;
|
|
72
|
+
});
|
|
73
|
+
let delta = 0;
|
|
74
|
+
nextScores.forEach((val, key) => {
|
|
75
|
+
const normalized = val / totalWeight;
|
|
76
|
+
const diff = normalized - (scores.get(key) || 0);
|
|
77
|
+
delta += diff * diff;
|
|
78
|
+
nextScores.set(key, normalized);
|
|
79
|
+
});
|
|
80
|
+
scores = nextScores;
|
|
81
|
+
if (Math.sqrt(delta) < tolerance) break;
|
|
82
|
+
}
|
|
83
|
+
return scores;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// src/core/ranker.ts
|
|
87
|
+
var parseLabel = (label) => {
|
|
88
|
+
const parts = label.split(" -> ");
|
|
89
|
+
return { file: parts[0] || "Unknown", name: parts[1] || label };
|
|
90
|
+
};
|
|
91
|
+
var identifyTopIssues = (graph, history2, redundantLabels2, violationMap) => {
|
|
92
|
+
const results = [];
|
|
93
|
+
const influence = calculateSpectralInfluence(graph);
|
|
94
|
+
const isEffect = (label) => label.includes("effect_L") || label.includes("useLayoutEffect") || label.includes("useInsertionEffect");
|
|
95
|
+
const isEvent = (label) => label.startsWith("Event_Tick_");
|
|
96
|
+
const eventSignatures = /* @__PURE__ */ new Map();
|
|
97
|
+
const drivers = Array.from(graph.entries()).filter(([label, targets]) => {
|
|
98
|
+
if (targets.size === 0) return false;
|
|
99
|
+
if (isEvent(label)) {
|
|
100
|
+
const validTargets = Array.from(targets.keys()).filter((t) => {
|
|
101
|
+
const meta2 = history2.get(t);
|
|
102
|
+
return meta2 && meta2.role !== "context" /* CONTEXT */;
|
|
103
|
+
});
|
|
104
|
+
if (validTargets.length > 1) {
|
|
105
|
+
const signature = validTargets.sort().join("|");
|
|
106
|
+
const existing = eventSignatures.get(signature);
|
|
107
|
+
if (existing) {
|
|
108
|
+
existing.count++;
|
|
109
|
+
existing.score = Math.max(existing.score, influence.get(label) || 0);
|
|
110
|
+
} else {
|
|
111
|
+
eventSignatures.set(signature, {
|
|
112
|
+
count: 1,
|
|
113
|
+
score: influence.get(label) || 0,
|
|
114
|
+
targets: validTargets
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
const meta = history2.get(label);
|
|
121
|
+
if (meta?.role === "context" /* CONTEXT */) return false;
|
|
122
|
+
if (meta?.role === "proj" /* PROJECTION */) return false;
|
|
123
|
+
const score = influence.get(label) || 0;
|
|
124
|
+
if (isEffect(label) && targets.size > 0) return true;
|
|
125
|
+
if (targets.size < 2 && score < 0.05) return false;
|
|
126
|
+
return true;
|
|
127
|
+
}).sort((a, b) => {
|
|
128
|
+
const scoreA = influence.get(a[0]) || 0;
|
|
129
|
+
const scoreB = influence.get(b[0]) || 0;
|
|
130
|
+
return scoreB - scoreA;
|
|
131
|
+
});
|
|
132
|
+
const sortedEvents = Array.from(eventSignatures.entries()).sort((a, b) => b[1].targets.length - a[1].targets.length);
|
|
133
|
+
sortedEvents.slice(0, 2).forEach(([sig, data]) => {
|
|
134
|
+
const primaryVictim = parseLabel(data.targets[0]);
|
|
135
|
+
const smartLabel = `${primaryVictim.file} -> Global Event (${primaryVictim.name})`;
|
|
136
|
+
results.push({
|
|
137
|
+
label: smartLabel,
|
|
138
|
+
metric: "influence",
|
|
139
|
+
score: 1,
|
|
140
|
+
reason: `Global Sync Event: An external trigger is updating ${data.targets.length} roots simultaneously. Occurred ${data.count} times.`,
|
|
141
|
+
violations: data.targets.map((t) => ({
|
|
142
|
+
type: "causal_leak",
|
|
143
|
+
target: t
|
|
144
|
+
}))
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
drivers.slice(0, 3 - results.length).forEach(([label, targets]) => {
|
|
148
|
+
const targetNames = Array.from(targets.keys());
|
|
149
|
+
if (isEffect(label)) {
|
|
150
|
+
results.push({
|
|
151
|
+
label,
|
|
152
|
+
metric: "influence",
|
|
153
|
+
score: influence.get(label) || 0,
|
|
154
|
+
reason: `Side-Effect Driver: Hook writes to state during render.`,
|
|
155
|
+
violations: targetNames.map((t) => ({ type: "causal_leak", target: t }))
|
|
156
|
+
});
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
results.push({
|
|
160
|
+
label,
|
|
161
|
+
metric: "influence",
|
|
162
|
+
score: influence.get(label) || targets.size,
|
|
163
|
+
reason: `Sync Driver: Acts as a "Prime Mover" for ${targets.size} downstream signals.`,
|
|
164
|
+
violations: targetNames.map((t) => ({ type: "causal_leak", target: t }))
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
if (results.length === 0) {
|
|
168
|
+
const sortedDensity = Array.from(history2.entries()).filter(
|
|
169
|
+
([label, meta]) => meta.role === "local" /* LOCAL */ && !redundantLabels2.has(label) && meta.density > 25
|
|
170
|
+
).sort((a, b) => b[1].density - a[1].density);
|
|
171
|
+
sortedDensity.slice(0, 3).forEach(([label, meta]) => {
|
|
172
|
+
results.push({
|
|
173
|
+
label,
|
|
174
|
+
metric: "density",
|
|
175
|
+
score: meta.density,
|
|
176
|
+
reason: `High Frequency: potential main-thread saturation.`,
|
|
177
|
+
violations: []
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return results;
|
|
182
|
+
};
|
|
183
|
+
|
|
51
184
|
// src/core/logger.ts
|
|
52
185
|
var isWeb = typeof window !== "undefined" && typeof window.document !== "undefined";
|
|
53
186
|
var LAST_LOG_TIMES = /* @__PURE__ */ new Map();
|
|
54
187
|
var LOG_COOLDOWN = 3e3;
|
|
188
|
+
var THEME = {
|
|
189
|
+
identity: "#6C5CE7",
|
|
190
|
+
// Purple (Brand)
|
|
191
|
+
problem: "#D63031",
|
|
192
|
+
// Red (Bugs)
|
|
193
|
+
solution: "#FBC531",
|
|
194
|
+
// Yellow (Fixes)
|
|
195
|
+
context: "#0984E3",
|
|
196
|
+
// Blue (Locations)
|
|
197
|
+
muted: "#9AA0A6",
|
|
198
|
+
// Gray (Metadata)
|
|
199
|
+
border: "#2E2E35",
|
|
200
|
+
success: "#00b894"
|
|
201
|
+
// Green (Good Score)
|
|
202
|
+
};
|
|
55
203
|
var STYLES = {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
204
|
+
// Structure
|
|
205
|
+
basis: `background: ${THEME.identity}; color: white; font-weight: bold; padding: 2px 6px; border-radius: 3px;`,
|
|
206
|
+
headerIdentity: `background: ${THEME.identity}; color: white; font-weight: bold; padding: 4px 8px; border-radius: 4px;`,
|
|
207
|
+
headerProblem: `background: ${THEME.problem}; color: white; font-weight: bold; padding: 4px 8px; border-radius: 4px;`,
|
|
208
|
+
version: `background: #a29bfe; color: #2d3436; padding: 2px 6px; border-radius: 3px; margin-left: -4px;`,
|
|
209
|
+
// Actions
|
|
210
|
+
actionLabel: `color: ${THEME.solution}; font-weight: bold;`,
|
|
211
|
+
actionPill: `color: ${THEME.solution}; font-weight: bold; border: 1px solid ${THEME.solution}; padding: 0 4px; border-radius: 3px;`,
|
|
212
|
+
// Context
|
|
213
|
+
impactLabel: `color: ${THEME.context}; font-weight: bold;`,
|
|
214
|
+
location: `color: ${THEME.context}; font-family: monospace; font-weight: bold;`,
|
|
215
|
+
// Text
|
|
216
|
+
subText: `color: ${THEME.muted}; font-size: 11px;`,
|
|
64
217
|
bold: "font-weight: bold;",
|
|
65
|
-
|
|
66
|
-
warning: "color: #d63031; font-weight: bold; border: 1px solid #d63031; padding: 0 4px; border-radius: 3px;"
|
|
218
|
+
label: "background: #dfe6e9; color: #2d3436; padding: 0 4px; border-radius: 3px; font-family: monospace; font-weight: bold; border: 1px solid #b2bec3;"
|
|
67
219
|
};
|
|
68
|
-
var
|
|
220
|
+
var parseLabel2 = (label) => {
|
|
69
221
|
const parts = label.split(" -> ");
|
|
70
222
|
return { file: parts[0] || "Unknown", name: parts[1] || label };
|
|
71
223
|
};
|
|
@@ -78,57 +230,103 @@ var shouldLog = (key) => {
|
|
|
78
230
|
}
|
|
79
231
|
return false;
|
|
80
232
|
};
|
|
81
|
-
var
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const isContextMirror = metaA.role === "local" && metaB.role === "context" || metaB.role === "local" && metaA.role === "context";
|
|
86
|
-
const local = metaA.role === "local" ? infoA : infoB;
|
|
87
|
-
const context = metaA.role === "context" ? infoA : infoB;
|
|
88
|
-
console.group(`%c \u264A BASIS | ${isContextMirror ? "CONTEXT MIRRORING" : "DUPLICATE STATE"} `, STYLES.headerRed);
|
|
89
|
-
console.log(`%c\u{1F4CD} Location: %c${infoA.file}`, STYLES.bold, STYLES.location);
|
|
90
|
-
if (isContextMirror) {
|
|
91
|
-
console.log(
|
|
92
|
-
`%cIssue:%c Local variable %c${local.name}%c is just a copy of Global Context %c${context.name}%c.
|
|
93
|
-
%cConfidence: ${(sim * 100).toFixed(0)}%`,
|
|
94
|
-
STYLES.bold,
|
|
95
|
-
"",
|
|
96
|
-
STYLES.label,
|
|
97
|
-
"",
|
|
98
|
-
STYLES.label,
|
|
99
|
-
"",
|
|
100
|
-
STYLES.subText
|
|
101
|
-
);
|
|
102
|
-
console.log(`%cFix:%c Use the context value directly to avoid state drift.`, STYLES.bold, STYLES.warning);
|
|
103
|
-
} else {
|
|
104
|
-
console.log(
|
|
105
|
-
`%cIssue:%c %c${infoA.name}%c and %c${infoB.name}%c are synchronized (${(sim * 100).toFixed(0)}% correlation).`,
|
|
106
|
-
STYLES.bold,
|
|
107
|
-
"",
|
|
108
|
-
STYLES.label,
|
|
109
|
-
"",
|
|
110
|
-
STYLES.label,
|
|
111
|
-
""
|
|
112
|
-
);
|
|
113
|
-
console.log(
|
|
114
|
-
`%cFix:%c Merge states or calculate %c${infoB.name}%c from %c${infoA.name}%c via %cuseMemo%c.`,
|
|
115
|
-
STYLES.bold,
|
|
116
|
-
"",
|
|
117
|
-
STYLES.label,
|
|
118
|
-
"",
|
|
119
|
-
STYLES.label,
|
|
120
|
-
"",
|
|
121
|
-
STYLES.action,
|
|
122
|
-
""
|
|
123
|
-
);
|
|
233
|
+
var isBooleanLike = (name) => /^(is|has|can|should|did|will|show|hide)(?=[A-Z_])/.test(name);
|
|
234
|
+
var getSuggestedFix = (issue, info) => {
|
|
235
|
+
if (issue.label.includes("Global Event")) {
|
|
236
|
+
return `These variables update together but live in different hooks/files. Consolidate them into a single %cuseReducer%c or atomic store update.`;
|
|
124
237
|
}
|
|
125
|
-
|
|
238
|
+
const violations = issue.violations || [];
|
|
239
|
+
const leaks = violations.filter((v) => v.type === "causal_leak");
|
|
240
|
+
const mirrors = violations.filter((v) => v.type === "context_mirror");
|
|
241
|
+
const duplicates = violations.filter((v) => v.type === "duplicate_state");
|
|
242
|
+
if (mirrors.length > 0) {
|
|
243
|
+
return `Local state is 'shadowing' Global Context. This creates two sources of truth. Delete the local state and consume the %cContext%c value directly.`;
|
|
244
|
+
}
|
|
245
|
+
if (leaks.length > 0) {
|
|
246
|
+
const targetName = parseLabel2(leaks[0].target).name;
|
|
247
|
+
if (issue.label.includes("effect")) {
|
|
248
|
+
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.`;
|
|
249
|
+
}
|
|
250
|
+
return `State cascading detected. ${info.name} triggers ${targetName} in a separate frame. Merge them into one object to update simultaneously.`;
|
|
251
|
+
}
|
|
252
|
+
if (duplicates.length > 0) {
|
|
253
|
+
if (isBooleanLike(info.name)) {
|
|
254
|
+
return `Boolean Explosion detected. Multiple flags are toggling in sync. Replace impossible states with a single %cstatus%c string ('idle' | 'loading' | 'success').`;
|
|
255
|
+
}
|
|
256
|
+
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.`;
|
|
257
|
+
}
|
|
258
|
+
if (issue.metric === "density") {
|
|
259
|
+
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.`;
|
|
260
|
+
}
|
|
261
|
+
return `Check the dependency chain of ${info.name}.`;
|
|
126
262
|
};
|
|
127
|
-
var displayHealthReport = (history2, threshold) => {
|
|
263
|
+
var displayHealthReport = (history2, threshold, violationMap) => {
|
|
128
264
|
if (!isWeb) return;
|
|
129
265
|
const entries = Array.from(history2.entries());
|
|
130
|
-
|
|
131
|
-
|
|
266
|
+
if (entries.length === 0) return;
|
|
267
|
+
const topIssues = identifyTopIssues(instance.graph, history2, instance.redundantLabels, violationMap);
|
|
268
|
+
console.group(`%c \u{1F4CA} BASIS | ARCHITECTURAL HEALTH REPORT `, STYLES.headerIdentity);
|
|
269
|
+
if (topIssues.length > 0) {
|
|
270
|
+
console.log(
|
|
271
|
+
`%c\u{1F3AF} REFACTOR PRIORITIES %c(PRIME MOVERS)`,
|
|
272
|
+
`font-weight: bold; color: ${THEME.identity}; margin-top: 10px;`,
|
|
273
|
+
`font-weight: normal; color: ${THEME.muted}; font-style: italic;`
|
|
274
|
+
);
|
|
275
|
+
topIssues.forEach((issue, idx) => {
|
|
276
|
+
const info = parseLabel2(issue.label);
|
|
277
|
+
const icon = issue.metric === "influence" ? "\u26A1" : "\u{1F4C8}";
|
|
278
|
+
const pColor = idx === 0 ? THEME.problem : idx === 1 ? THEME.solution : THEME.identity;
|
|
279
|
+
let displayName = info.name;
|
|
280
|
+
let displayFile = info.file;
|
|
281
|
+
if (issue.label.includes("Global Event")) {
|
|
282
|
+
displayName = info.name;
|
|
283
|
+
displayFile = info.file;
|
|
284
|
+
}
|
|
285
|
+
console.group(
|
|
286
|
+
` %c${idx + 1}%c ${icon} ${displayName} %c(${displayFile})`,
|
|
287
|
+
`background: ${pColor}; color: ${idx === 1 ? "black" : "white"}; border-radius: 50%; padding: 0 5px;`,
|
|
288
|
+
"font-family: monospace; font-weight: 700;",
|
|
289
|
+
`color: ${THEME.muted}; font-size: 10px; font-weight: normal; font-style: italic;`
|
|
290
|
+
);
|
|
291
|
+
console.log(`%c${issue.reason}`, `color: ${THEME.muted}; font-style: italic;`);
|
|
292
|
+
if (issue.violations.length > 0) {
|
|
293
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
294
|
+
issue.violations.forEach((v) => {
|
|
295
|
+
if (issue.label.includes("Global Event") && v.type === "context_mirror") return;
|
|
296
|
+
const { file, name } = parseLabel2(v.target);
|
|
297
|
+
if (!byFile.has(file)) byFile.set(file, []);
|
|
298
|
+
byFile.get(file).push(name);
|
|
299
|
+
});
|
|
300
|
+
const impactParts = [];
|
|
301
|
+
byFile.forEach((vars, file) => {
|
|
302
|
+
const varList = vars.join(", ");
|
|
303
|
+
impactParts.push(`${file} (${varList})`);
|
|
304
|
+
});
|
|
305
|
+
if (impactParts.length > 0) {
|
|
306
|
+
console.log(`%cImpacts: %c${impactParts.join(" + ")}`, STYLES.impactLabel, "");
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const fix = getSuggestedFix(issue, info);
|
|
310
|
+
const fixParts = fix.split("%c");
|
|
311
|
+
if (fixParts.length === 3) {
|
|
312
|
+
console.log(
|
|
313
|
+
`%cSolution: %c${fixParts[0]}%c${fixParts[1]}%c${fixParts[2]}`,
|
|
314
|
+
STYLES.actionLabel,
|
|
315
|
+
"",
|
|
316
|
+
STYLES.actionPill,
|
|
317
|
+
""
|
|
318
|
+
);
|
|
319
|
+
} else {
|
|
320
|
+
console.log(
|
|
321
|
+
`%cSolution: %c${fix}`,
|
|
322
|
+
STYLES.actionLabel,
|
|
323
|
+
""
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
console.groupEnd();
|
|
327
|
+
});
|
|
328
|
+
console.log("\n");
|
|
329
|
+
}
|
|
132
330
|
const clusters = [];
|
|
133
331
|
const processed = /* @__PURE__ */ new Set();
|
|
134
332
|
let independentCount = 0;
|
|
@@ -138,9 +336,8 @@ var displayHealthReport = (history2, threshold) => {
|
|
|
138
336
|
processed.add(labelA);
|
|
139
337
|
entries.forEach(([labelB, metaB]) => {
|
|
140
338
|
if (labelA === labelB || processed.has(labelB)) return;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (metaA.role === "context" && metaB.role === "context") return;
|
|
339
|
+
if (calculateCosineSimilarity(metaA.buffer, metaB.buffer) > threshold) {
|
|
340
|
+
if (metaA.role === "context" /* CONTEXT */ && metaB.role === "context" /* CONTEXT */) return;
|
|
144
341
|
currentCluster.push(labelB);
|
|
145
342
|
processed.add(labelB);
|
|
146
343
|
}
|
|
@@ -148,73 +345,149 @@ var displayHealthReport = (history2, threshold) => {
|
|
|
148
345
|
if (currentCluster.length > 1) clusters.push(currentCluster);
|
|
149
346
|
else independentCount++;
|
|
150
347
|
});
|
|
151
|
-
const
|
|
152
|
-
const
|
|
153
|
-
|
|
348
|
+
const totalVars = entries.length;
|
|
349
|
+
const redundancyScore = (independentCount + clusters.length) / totalVars * 100;
|
|
350
|
+
let internalEdges = 0;
|
|
351
|
+
instance.graph.forEach((targets, source) => {
|
|
352
|
+
if (source.startsWith("Event_Tick_")) return;
|
|
353
|
+
internalEdges += targets.size;
|
|
354
|
+
});
|
|
355
|
+
const causalPenalty = internalEdges / totalVars * 100;
|
|
356
|
+
let healthScore = redundancyScore - causalPenalty;
|
|
357
|
+
if (healthScore < 0) healthScore = 0;
|
|
358
|
+
const scoreColor = healthScore > 85 ? THEME.success : THEME.problem;
|
|
154
359
|
console.log(
|
|
155
|
-
`%
|
|
360
|
+
`%cSystem Efficiency: %c${healthScore.toFixed(1)}%`,
|
|
156
361
|
STYLES.bold,
|
|
157
|
-
`color: ${
|
|
158
|
-
STYLES.subText
|
|
362
|
+
`color: ${scoreColor}; font-weight: bold;`
|
|
159
363
|
);
|
|
364
|
+
console.log(`%cSources of Truth: ${independentCount + clusters.length}/${totalVars} | Causal Leaks: ${internalEdges}`, STYLES.subText);
|
|
160
365
|
if (clusters.length > 0) {
|
|
161
|
-
console.log(`%cDetected ${clusters.length} Sync Issues:`,
|
|
366
|
+
console.log(`%cDetected ${clusters.length} Sync Issues:`, `font-weight: bold; color: ${THEME.problem}; margin-top: 10px;`);
|
|
162
367
|
clusters.forEach((cluster, idx) => {
|
|
163
|
-
const clusterMetas = cluster.map((l) => ({
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
368
|
+
const clusterMetas = cluster.map((l) => ({
|
|
369
|
+
label: l,
|
|
370
|
+
meta: history2.get(l),
|
|
371
|
+
name: parseLabel2(l).name
|
|
372
|
+
}));
|
|
373
|
+
const hasCtx = clusterMetas.some((c) => c.meta.role === "context" /* CONTEXT */);
|
|
374
|
+
const names = clusterMetas.map((c) => `${c.meta.role === "context" /* CONTEXT */ ? "\u03A9 " : ""}${c.name}`).join(" \u27F7 ");
|
|
375
|
+
console.group(` %c${idx + 1}%c ${names}`, `background: ${THEME.problem}; color: white; border-radius: 50%; padding: 0 5px;`, "font-family: monospace; font-weight: bold;");
|
|
376
|
+
if (hasCtx) {
|
|
377
|
+
console.log(`%cDiagnosis: Context Mirroring. Local state is shadowing global context.`, `color: ${THEME.problem};`);
|
|
378
|
+
console.log(`%cSolution: Use context directly to avoid state drift.`, STYLES.actionLabel);
|
|
172
379
|
} else {
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
380
|
+
const boolKeywords = ["is", "has", "can", "should", "loading", "success", "error", "active", "enabled", "open", "visible"];
|
|
381
|
+
const boolCount = clusterMetas.filter(
|
|
382
|
+
(c) => boolKeywords.some((kw) => c.name.toLowerCase().startsWith(kw))
|
|
383
|
+
).length;
|
|
384
|
+
const isBoolExplosion = cluster.length > 2 && boolCount / cluster.length > 0.5;
|
|
385
|
+
if (isBoolExplosion) {
|
|
386
|
+
console.log(`%cDiagnosis:%c Boolean Explosion. Multiple booleans updating in sync.`, STYLES.bold, "");
|
|
387
|
+
console.log(`%cSolution:%c Combine into a single %cstatus%c string or a %creducer%c.`, STYLES.actionLabel, "", STYLES.actionPill, "", STYLES.actionPill, "");
|
|
388
|
+
} else if (cluster.length > 2) {
|
|
389
|
+
console.log(`%cDiagnosis:%c Sibling Updates. These states respond to the same event.`, STYLES.bold, "");
|
|
390
|
+
console.log(`%cSolution:%c This may be intentional. If not, consolidate into a %creducer%c.`, STYLES.actionLabel, "", STYLES.actionPill, "");
|
|
177
391
|
} else {
|
|
178
392
|
console.log(`%cDiagnosis:%c Redundant State. Variables always change together.`, STYLES.bold, "");
|
|
179
|
-
console.log(`%cSolution:%c Derive one from the other via %cuseMemo%c.`, STYLES.
|
|
393
|
+
console.log(`%cSolution:%c Derive one from the other via %cuseMemo%c.`, STYLES.actionLabel, "", STYLES.actionPill, "");
|
|
180
394
|
}
|
|
181
395
|
}
|
|
182
396
|
console.groupEnd();
|
|
183
397
|
});
|
|
184
398
|
} else {
|
|
185
|
-
console.log("%c\u2728 Your architecture is clean. No redundant state detected.",
|
|
399
|
+
console.log("%c\u2728 Your architecture is clean. No redundant state detected.", `color: ${THEME.success}; font-weight: bold;`);
|
|
400
|
+
}
|
|
401
|
+
console.groupEnd();
|
|
402
|
+
};
|
|
403
|
+
var displayRedundancyAlert = (labelA, metaA, labelB, metaB, sim) => {
|
|
404
|
+
if (!isWeb || !shouldLog(`redundant-${labelA}-${labelB}`)) return;
|
|
405
|
+
const infoA = parseLabel2(labelA);
|
|
406
|
+
const infoB = parseLabel2(labelB);
|
|
407
|
+
const isContextMirror = metaA.role === "local" /* LOCAL */ && metaB.role === "context" /* CONTEXT */ || metaB.role === "local" /* LOCAL */ && metaA.role === "context" /* CONTEXT */;
|
|
408
|
+
console.group(`%c \u264A BASIS | ${isContextMirror ? "CONTEXT MIRRORING" : "DUPLICATE STATE"} `, STYLES.headerProblem);
|
|
409
|
+
console.log(`%c\u{1F4CD} Location: %c${infoA.file}`, STYLES.bold, STYLES.location);
|
|
410
|
+
console.log(`%cIssue:%c ${infoA.name} and ${infoB.name} are synchronized (${(sim * 100).toFixed(0)}%).`, STYLES.bold, "");
|
|
411
|
+
if (isContextMirror) {
|
|
412
|
+
console.log(
|
|
413
|
+
`%cFix:%c Local state is 'shadowing' Global Context. Delete the local state and consume the %cContext%c value directly.`,
|
|
414
|
+
STYLES.bold,
|
|
415
|
+
"",
|
|
416
|
+
STYLES.actionPill,
|
|
417
|
+
""
|
|
418
|
+
);
|
|
419
|
+
} else {
|
|
420
|
+
if (isBooleanLike(infoA.name) || isBooleanLike(infoB.name)) {
|
|
421
|
+
console.log(
|
|
422
|
+
`%cFix:%c Boolean Explosion detected. Merge flags into a single %cstatus%c string or %cuseReducer%c.`,
|
|
423
|
+
STYLES.bold,
|
|
424
|
+
"",
|
|
425
|
+
STYLES.actionPill,
|
|
426
|
+
"",
|
|
427
|
+
STYLES.actionPill,
|
|
428
|
+
""
|
|
429
|
+
);
|
|
430
|
+
} else {
|
|
431
|
+
console.log(
|
|
432
|
+
`%cFix:%c Redundant State detected. Derive %c${infoB.name}%c from %c${infoA.name}%c during render, or use %cuseMemo%c.`,
|
|
433
|
+
STYLES.bold,
|
|
434
|
+
"",
|
|
435
|
+
STYLES.label,
|
|
436
|
+
"",
|
|
437
|
+
STYLES.label,
|
|
438
|
+
"",
|
|
439
|
+
STYLES.actionPill,
|
|
440
|
+
""
|
|
441
|
+
);
|
|
442
|
+
}
|
|
186
443
|
}
|
|
187
444
|
console.groupEnd();
|
|
188
445
|
};
|
|
189
446
|
var displayCausalHint = (targetLabel, targetMeta, sourceLabel, sourceMeta) => {
|
|
190
447
|
if (!isWeb || !shouldLog(`causal-${sourceLabel}-${targetLabel}`)) return;
|
|
191
|
-
const target =
|
|
192
|
-
const source =
|
|
193
|
-
const
|
|
194
|
-
|
|
448
|
+
const target = parseLabel2(targetLabel);
|
|
449
|
+
const source = parseLabel2(sourceLabel);
|
|
450
|
+
const isCtx = sourceMeta.role === "context" /* CONTEXT */;
|
|
451
|
+
const isEffect = sourceLabel.includes("effect") || sourceLabel.includes("useLayoutEffect");
|
|
452
|
+
console.groupCollapsed(`%c \u26A1 BASIS | ${isCtx ? "CONTEXT SYNC LEAK" : "DOUBLE RENDER"} `, STYLES.headerProblem);
|
|
195
453
|
console.log(`%c\u{1F4CD} Location: %c${target.file}`, STYLES.bold, STYLES.location);
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
console.log(
|
|
454
|
+
console.log(`%cIssue:%c ${source.name} triggers ${target.name} in separate frames.`, STYLES.bold, "");
|
|
455
|
+
if (isEffect) {
|
|
456
|
+
console.log(
|
|
457
|
+
`%cFix:%c Derive %c${target.name}%c during the render phase (remove effect) or wrap in %cuseMemo%c.`,
|
|
458
|
+
STYLES.bold,
|
|
459
|
+
"",
|
|
460
|
+
STYLES.label,
|
|
461
|
+
"",
|
|
462
|
+
STYLES.actionPill,
|
|
463
|
+
""
|
|
464
|
+
);
|
|
199
465
|
} else {
|
|
200
|
-
console.log(
|
|
466
|
+
console.log(
|
|
467
|
+
`%cFix:%c Merge %c${target.name}%c with %c${source.name}%c into a single state update.`,
|
|
468
|
+
STYLES.bold,
|
|
469
|
+
"",
|
|
470
|
+
STYLES.label,
|
|
471
|
+
"",
|
|
472
|
+
STYLES.label,
|
|
473
|
+
""
|
|
474
|
+
);
|
|
201
475
|
}
|
|
202
|
-
console.log(`%cFix:%c Derive %c${target.name}%c during the first render.`, STYLES.bold, STYLES.action, STYLES.label, "");
|
|
203
476
|
console.groupEnd();
|
|
204
477
|
};
|
|
205
478
|
var displayViolentBreaker = (label, count, threshold) => {
|
|
206
479
|
if (!isWeb) return;
|
|
207
480
|
const parts = label.split(" -> ");
|
|
208
|
-
console.group(`%c \u{1F6D1} BASIS CRITICAL | CIRCUIT BREAKER `,
|
|
481
|
+
console.group(`%c \u{1F6D1} BASIS CRITICAL | CIRCUIT BREAKER `, STYLES.headerProblem);
|
|
209
482
|
console.error(`INFINITE LOOP DETECTED
|
|
210
483
|
Variable: ${parts[1] || label}
|
|
211
484
|
Frequency: ${count} updates/sec`);
|
|
212
|
-
console.log(`%cACTION: Update BLOCKED to prevent browser freeze.`,
|
|
485
|
+
console.log(`%cACTION: Update BLOCKED to prevent browser freeze.`, `color: ${THEME.problem}; font-weight: bold;`);
|
|
213
486
|
console.groupEnd();
|
|
214
487
|
};
|
|
215
488
|
var displayBootLog = (windowSize) => {
|
|
216
489
|
if (!isWeb) return;
|
|
217
|
-
console.log(`%cBasis%cAuditor%c
|
|
490
|
+
console.log(`%cBasis%cAuditor%c "Graph Era" (Window: ${windowSize})`, STYLES.basis, STYLES.version, `color: ${THEME.muted}; font-style: italic; margin-left: 8px;`);
|
|
218
491
|
};
|
|
219
492
|
|
|
220
493
|
// src/core/constants.ts
|
|
@@ -224,10 +497,37 @@ var LOOP_THRESHOLD = 150;
|
|
|
224
497
|
var VOLATILITY_THRESHOLD = 25;
|
|
225
498
|
|
|
226
499
|
// src/core/analysis.ts
|
|
500
|
+
var CAUSAL_MARGIN = 0.05;
|
|
501
|
+
var isEventDriven = (label, graph) => {
|
|
502
|
+
for (const [parent, targets] of graph.entries()) {
|
|
503
|
+
if (parent.startsWith("Event_Tick_") && targets.has(label)) {
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return false;
|
|
508
|
+
};
|
|
227
509
|
var calculateAllSimilarities = (entryA, entryB) => {
|
|
228
|
-
const sync = calculateSimilarityCircular(
|
|
229
|
-
|
|
230
|
-
|
|
510
|
+
const sync = calculateSimilarityCircular(
|
|
511
|
+
entryA.meta.buffer,
|
|
512
|
+
entryA.meta.head,
|
|
513
|
+
entryB.meta.buffer,
|
|
514
|
+
entryB.meta.head,
|
|
515
|
+
0
|
|
516
|
+
);
|
|
517
|
+
const bA = calculateSimilarityCircular(
|
|
518
|
+
entryA.meta.buffer,
|
|
519
|
+
entryA.meta.head,
|
|
520
|
+
entryB.meta.buffer,
|
|
521
|
+
entryB.meta.head,
|
|
522
|
+
1
|
|
523
|
+
);
|
|
524
|
+
const aB = calculateSimilarityCircular(
|
|
525
|
+
entryA.meta.buffer,
|
|
526
|
+
entryA.meta.head,
|
|
527
|
+
entryB.meta.buffer,
|
|
528
|
+
entryB.meta.head,
|
|
529
|
+
-1
|
|
530
|
+
);
|
|
231
531
|
return { sync, bA, aB, max: Math.max(sync, bA, aB) };
|
|
232
532
|
};
|
|
233
533
|
var shouldSkipComparison = (entryA, entryB, dirtyLabels2) => {
|
|
@@ -235,49 +535,78 @@ var shouldSkipComparison = (entryA, entryB, dirtyLabels2) => {
|
|
|
235
535
|
if (dirtyLabels2.has(entryB.label) && entryA.label > entryB.label) return true;
|
|
236
536
|
return false;
|
|
237
537
|
};
|
|
238
|
-
var
|
|
538
|
+
var pushViolation = (map, source, detail) => {
|
|
539
|
+
if (!map.has(source)) {
|
|
540
|
+
map.set(source, []);
|
|
541
|
+
}
|
|
542
|
+
const list = map.get(source);
|
|
543
|
+
const exists = list.some(
|
|
544
|
+
(v) => v.type === detail.type && v.target === detail.target
|
|
545
|
+
);
|
|
546
|
+
if (!exists) {
|
|
547
|
+
list.push(detail);
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
var detectRedundancy = (entryA, entryB, similarities, redundantSet, violationMap) => {
|
|
239
551
|
const roleA = entryA.meta.role;
|
|
240
552
|
const roleB = entryB.meta.role;
|
|
241
553
|
if (roleA === "context" /* CONTEXT */ && roleB === "context" /* CONTEXT */) return;
|
|
242
554
|
if (entryA.meta.density < 2 || entryB.meta.density < 2) return;
|
|
243
555
|
if (roleA === "local" /* LOCAL */ && roleB === "context" /* CONTEXT */) {
|
|
244
556
|
redundantSet.add(entryA.label);
|
|
557
|
+
pushViolation(violationMap, entryB.label, { type: "context_mirror", target: entryA.label, similarity: similarities.max });
|
|
245
558
|
displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta, similarities.max);
|
|
246
559
|
} else if (roleA === "context" /* CONTEXT */ && roleB === "local" /* LOCAL */) {
|
|
247
560
|
redundantSet.add(entryB.label);
|
|
561
|
+
pushViolation(violationMap, entryA.label, { type: "context_mirror", target: entryB.label, similarity: similarities.max });
|
|
248
562
|
displayRedundancyAlert(entryB.label, entryB.meta, entryA.label, entryA.meta, similarities.max);
|
|
249
563
|
} else if (roleA === "local" /* LOCAL */ && roleB === "local" /* LOCAL */) {
|
|
250
564
|
redundantSet.add(entryA.label);
|
|
251
565
|
redundantSet.add(entryB.label);
|
|
566
|
+
pushViolation(violationMap, entryA.label, { type: "duplicate_state", target: entryB.label, similarity: similarities.max });
|
|
567
|
+
pushViolation(violationMap, entryB.label, { type: "duplicate_state", target: entryA.label, similarity: similarities.max });
|
|
252
568
|
displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta, similarities.max);
|
|
253
569
|
}
|
|
254
570
|
};
|
|
255
|
-
var detectCausalLeak = (entryA, entryB, similarities) => {
|
|
571
|
+
var detectCausalLeak = (entryA, entryB, similarities, violationMap, graph) => {
|
|
256
572
|
if (entryA.isVolatile || entryB.isVolatile) return;
|
|
573
|
+
if (similarities.max - similarities.sync < CAUSAL_MARGIN) return;
|
|
574
|
+
const addLeak = (source, target) => {
|
|
575
|
+
if (isEventDriven(target, graph)) return;
|
|
576
|
+
if (!violationMap.has(source)) {
|
|
577
|
+
violationMap.set(source, []);
|
|
578
|
+
}
|
|
579
|
+
violationMap.get(source).push({ type: "causal_leak", target });
|
|
580
|
+
const sourceEntry = source === entryA.label ? entryA : entryB;
|
|
581
|
+
const targetEntry = source === entryA.label ? entryB : entryA;
|
|
582
|
+
displayCausalHint(target, targetEntry.meta, source, sourceEntry.meta);
|
|
583
|
+
};
|
|
257
584
|
if (similarities.bA === similarities.max) {
|
|
258
|
-
|
|
585
|
+
addLeak(entryA.label, entryB.label);
|
|
259
586
|
} else if (similarities.aB === similarities.max) {
|
|
260
|
-
|
|
587
|
+
addLeak(entryB.label, entryA.label);
|
|
261
588
|
}
|
|
262
589
|
};
|
|
263
|
-
var detectSubspaceOverlap = (dirtyEntries, allEntries, redundantSet, dirtyLabels2) => {
|
|
590
|
+
var detectSubspaceOverlap = (dirtyEntries, allEntries, redundantSet, dirtyLabels2, graph) => {
|
|
264
591
|
let compCount = 0;
|
|
592
|
+
const violationMap = /* @__PURE__ */ new Map();
|
|
265
593
|
for (const entryA of dirtyEntries) {
|
|
266
594
|
for (const entryB of allEntries) {
|
|
267
595
|
if (shouldSkipComparison(entryA, entryB, dirtyLabels2)) continue;
|
|
268
596
|
compCount++;
|
|
269
597
|
const similarities = calculateAllSimilarities(entryA, entryB);
|
|
270
598
|
if (similarities.max > SIMILARITY_THRESHOLD) {
|
|
271
|
-
detectRedundancy(entryA, entryB, similarities, redundantSet);
|
|
272
|
-
detectCausalLeak(entryA, entryB, similarities);
|
|
599
|
+
detectRedundancy(entryA, entryB, similarities, redundantSet, violationMap);
|
|
600
|
+
detectCausalLeak(entryA, entryB, similarities, violationMap, graph);
|
|
273
601
|
}
|
|
274
602
|
}
|
|
275
603
|
}
|
|
276
|
-
return compCount;
|
|
604
|
+
return { compCount, violationMap };
|
|
277
605
|
};
|
|
278
606
|
|
|
279
607
|
// src/engine.ts
|
|
280
608
|
var BASIS_INSTANCE_KEY = /* @__PURE__ */ Symbol.for("__basis_engine_instance__");
|
|
609
|
+
var EVENT_TTL = 1e4;
|
|
281
610
|
var NULL_SIGNAL = {
|
|
282
611
|
role: "proj" /* PROJECTION */,
|
|
283
612
|
buffer: new Uint8Array(0),
|
|
@@ -285,6 +614,30 @@ var NULL_SIGNAL = {
|
|
|
285
614
|
density: 0,
|
|
286
615
|
options: {}
|
|
287
616
|
};
|
|
617
|
+
var activeEventId = null;
|
|
618
|
+
var activeEventTimer = null;
|
|
619
|
+
var pruneGraph = () => {
|
|
620
|
+
const now = Date.now();
|
|
621
|
+
for (const source of instance.graph.keys()) {
|
|
622
|
+
if (source.startsWith("Event_Tick_")) {
|
|
623
|
+
const parts = source.split("_");
|
|
624
|
+
const timestamp = parseInt(parts[2], 10);
|
|
625
|
+
if (now - timestamp > EVENT_TTL) {
|
|
626
|
+
instance.graph.delete(source);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
var getEventId = () => {
|
|
632
|
+
if (!activeEventId) {
|
|
633
|
+
activeEventId = `Event_Tick_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`;
|
|
634
|
+
if (activeEventTimer) clearTimeout(activeEventTimer);
|
|
635
|
+
activeEventTimer = setTimeout(() => {
|
|
636
|
+
activeEventId = null;
|
|
637
|
+
}, 0);
|
|
638
|
+
}
|
|
639
|
+
return activeEventId;
|
|
640
|
+
};
|
|
288
641
|
var getGlobalInstance = () => {
|
|
289
642
|
const root = globalThis;
|
|
290
643
|
if (!root[BASIS_INSTANCE_KEY]) {
|
|
@@ -297,7 +650,10 @@ var getGlobalInstance = () => {
|
|
|
297
650
|
tick: 0,
|
|
298
651
|
isBatching: false,
|
|
299
652
|
currentEffectSource: null,
|
|
653
|
+
lastStateUpdate: null,
|
|
300
654
|
pausedVariables: /* @__PURE__ */ new Set(),
|
|
655
|
+
graph: /* @__PURE__ */ new Map(),
|
|
656
|
+
violationMap: /* @__PURE__ */ new Map(),
|
|
301
657
|
metrics: {
|
|
302
658
|
lastAnalysisTimeMs: 0,
|
|
303
659
|
comparisonCount: 0,
|
|
@@ -327,6 +683,14 @@ var calculateTickEntropy = (tickIdx) => {
|
|
|
327
683
|
});
|
|
328
684
|
return 1 - activeCount / total;
|
|
329
685
|
};
|
|
686
|
+
var recordEdge = (source, target) => {
|
|
687
|
+
if (!source || !target || source === target) return;
|
|
688
|
+
if (!instance.graph.has(source)) {
|
|
689
|
+
instance.graph.set(source, /* @__PURE__ */ new Map());
|
|
690
|
+
}
|
|
691
|
+
const targets = instance.graph.get(source);
|
|
692
|
+
targets.set(target, (targets.get(target) || 0) + 1);
|
|
693
|
+
};
|
|
330
694
|
var analyzeBasis = () => {
|
|
331
695
|
if (!instance.config.debug || dirtyLabels.size === 0) {
|
|
332
696
|
return;
|
|
@@ -357,16 +721,33 @@ var analyzeBasis = () => {
|
|
|
357
721
|
nextRedundant.add(l);
|
|
358
722
|
}
|
|
359
723
|
});
|
|
360
|
-
const compCount = detectSubspaceOverlap(
|
|
724
|
+
const { compCount, violationMap } = detectSubspaceOverlap(
|
|
361
725
|
dirtyEntries,
|
|
362
726
|
allEntries,
|
|
363
727
|
nextRedundant,
|
|
364
|
-
snapshot
|
|
728
|
+
snapshot,
|
|
729
|
+
instance.graph
|
|
365
730
|
);
|
|
366
731
|
instance.redundantLabels.clear();
|
|
367
732
|
nextRedundant.forEach((l) => {
|
|
368
733
|
instance.redundantLabels.add(l);
|
|
369
734
|
});
|
|
735
|
+
violationMap.forEach((newList, label) => {
|
|
736
|
+
const existing = instance.violationMap.get(label) || [];
|
|
737
|
+
newList.forEach((detail) => {
|
|
738
|
+
const alreadyExists = existing.some(
|
|
739
|
+
(v) => v.type === detail.type && v.target === detail.target
|
|
740
|
+
);
|
|
741
|
+
if (!alreadyExists) {
|
|
742
|
+
existing.push(detail);
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
instance.violationMap.set(label, existing);
|
|
746
|
+
});
|
|
747
|
+
if (instance.violationMap.size > 500) {
|
|
748
|
+
const keys = Array.from(instance.violationMap.keys()).slice(0, 200);
|
|
749
|
+
keys.forEach((k) => instance.violationMap.delete(k));
|
|
750
|
+
}
|
|
370
751
|
instance.metrics.lastAnalysisTimeMs = performance.now() - analysisStart;
|
|
371
752
|
instance.metrics.comparisonCount = compCount;
|
|
372
753
|
instance.metrics.lastAnalysisTimestamp = Date.now();
|
|
@@ -388,6 +769,7 @@ var processHeartbeat = () => {
|
|
|
388
769
|
instance.currentTickBatch.clear();
|
|
389
770
|
currentTickRegistry = {};
|
|
390
771
|
instance.isBatching = false;
|
|
772
|
+
instance.lastStateUpdate = null;
|
|
391
773
|
if (dirtyLabels.size > 0) {
|
|
392
774
|
analyzeBasis();
|
|
393
775
|
}
|
|
@@ -399,6 +781,7 @@ var recordUpdate = (label) => {
|
|
|
399
781
|
if (now - instance.lastCleanup > 1e3) {
|
|
400
782
|
instance.loopCounters.clear();
|
|
401
783
|
instance.lastCleanup = now;
|
|
784
|
+
pruneGraph();
|
|
402
785
|
}
|
|
403
786
|
const count = (instance.loopCounters.get(label) || 0) + 1;
|
|
404
787
|
instance.loopCounters.set(label, count);
|
|
@@ -407,17 +790,25 @@ var recordUpdate = (label) => {
|
|
|
407
790
|
instance.pausedVariables.add(label);
|
|
408
791
|
return false;
|
|
409
792
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
793
|
+
const meta = instance.history.get(label);
|
|
794
|
+
let edgeSource = null;
|
|
795
|
+
if (instance.currentEffectSource) {
|
|
796
|
+
edgeSource = instance.currentEffectSource;
|
|
797
|
+
} else {
|
|
798
|
+
edgeSource = getEventId();
|
|
799
|
+
}
|
|
800
|
+
if (edgeSource && edgeSource !== label) {
|
|
801
|
+
recordEdge(edgeSource, label);
|
|
802
|
+
if (instance.currentEffectSource && instance.currentEffectSource !== label) {
|
|
803
|
+
const sourceMeta = instance.history.get(instance.currentEffectSource) || NULL_SIGNAL;
|
|
804
|
+
if (meta && sourceMeta && meta.density < VOLATILITY_THRESHOLD && sourceMeta.density < VOLATILITY_THRESHOLD) {
|
|
805
|
+
displayCausalHint(label, meta, instance.currentEffectSource, sourceMeta);
|
|
418
806
|
}
|
|
419
807
|
}
|
|
420
808
|
}
|
|
809
|
+
if (meta && meta.role === "local" /* LOCAL */ && !instance.currentEffectSource) {
|
|
810
|
+
instance.lastStateUpdate = label;
|
|
811
|
+
}
|
|
421
812
|
if (currentTickRegistry[label]) return true;
|
|
422
813
|
currentTickRegistry[label] = true;
|
|
423
814
|
instance.currentTickBatch.add(label);
|
|
@@ -461,10 +852,10 @@ var endEffectTracking = () => {
|
|
|
461
852
|
};
|
|
462
853
|
var printBasisHealthReport = (threshold = 0.5) => {
|
|
463
854
|
if (!instance.config.debug) return;
|
|
464
|
-
displayHealthReport(instance.history, threshold);
|
|
855
|
+
displayHealthReport(instance.history, threshold, instance.violationMap);
|
|
465
856
|
};
|
|
466
857
|
var getBasisMetrics = () => ({
|
|
467
|
-
engine: "v0.
|
|
858
|
+
engine: "v0.6.x",
|
|
468
859
|
hooks: instance.history.size,
|
|
469
860
|
analysis_ms: instance.metrics.lastAnalysisTimeMs.toFixed(3),
|
|
470
861
|
entropy: instance.metrics.systemEntropy.toFixed(3)
|
|
@@ -791,7 +1182,11 @@ var HUDHeader = ({ isExpanded }) => /* @__PURE__ */ jsxs(
|
|
|
791
1182
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
792
1183
|
var BasisContext = createContext2({ debug: false });
|
|
793
1184
|
var isWeb2 = typeof window !== "undefined" && typeof window.document !== "undefined";
|
|
794
|
-
var BasisProvider = ({
|
|
1185
|
+
var BasisProvider = ({
|
|
1186
|
+
children,
|
|
1187
|
+
debug = true,
|
|
1188
|
+
showHUD = true
|
|
1189
|
+
}) => {
|
|
795
1190
|
useLayoutEffect2(() => {
|
|
796
1191
|
configureBasis({ debug });
|
|
797
1192
|
if (isWeb2) {
|
|
@@ -800,7 +1195,7 @@ var BasisProvider = ({ children, debug = true }) => {
|
|
|
800
1195
|
}, [debug]);
|
|
801
1196
|
return /* @__PURE__ */ jsxs2(BasisContext.Provider, { value: { debug }, children: [
|
|
802
1197
|
children,
|
|
803
|
-
debug && isWeb2 && /* @__PURE__ */ jsx2(BasisHUD, {})
|
|
1198
|
+
debug && showHUD && isWeb2 && /* @__PURE__ */ jsx2(BasisHUD, {})
|
|
804
1199
|
] });
|
|
805
1200
|
};
|
|
806
1201
|
var useBasisConfig = () => useContext2(BasisContext);
|