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