react-state-basis 0.5.1 → 0.6.1
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 +53 -17
- package/dist/chunk-O2ZOR3L2.mjs +869 -0
- package/dist/chunk-O2ZOR3L2.mjs.map +1 -0
- package/dist/index.js +534 -126
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +28 -464
- package/dist/index.mjs.map +1 -1
- package/dist/integrations/zustand-production.d.mts +3 -0
- package/dist/integrations/zustand-production.d.ts +3 -0
- package/dist/integrations/zustand-production.js +31 -0
- package/dist/integrations/zustand-production.js.map +1 -0
- package/dist/integrations/zustand-production.mjs +6 -0
- package/dist/integrations/zustand-production.mjs.map +1 -0
- package/dist/integrations/zustand.d.mts +6 -0
- package/dist/integrations/zustand.d.ts +6 -0
- package/dist/integrations/zustand.js +887 -0
- package/dist/integrations/zustand.js.map +1 -0
- package/dist/integrations/zustand.mjs +33 -0
- package/dist/integrations/zustand.mjs.map +1 -0
- package/package.json +22 -3
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,159 @@ 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(
|
|
417
|
+
(c) => c.meta.role === "context" /* CONTEXT */ || c.meta.role === "store" /* STORE */
|
|
418
|
+
);
|
|
419
|
+
const names = clusterMetas.map((c) => {
|
|
420
|
+
const prefix = c.meta.role === "store" /* STORE */ ? "\u03A3 " : c.meta.role === "context" /* CONTEXT */ ? "\u03A9 " : "";
|
|
421
|
+
return `${prefix}${c.name}`;
|
|
422
|
+
}).join(" \u27F7 ");
|
|
423
|
+
console.group(` %c${idx + 1}%c ${names}`, `background: ${THEME.problem}; color: white; border-radius: 50%; padding: 0 5px;`, "font-family: monospace; font-weight: bold;");
|
|
424
|
+
if (hasCtx) {
|
|
425
|
+
const hasStore = clusterMetas.some((c) => c.meta.role === "store" /* STORE */);
|
|
426
|
+
const sourceType = hasStore ? "External Store" : "global context";
|
|
427
|
+
console.log(`%cDiagnosis: ${hasStore ? "Store" : "Context"} Mirroring. Local state is shadowing ${sourceType}.`, `color: ${THEME.problem};`);
|
|
428
|
+
console.log(`%cSolution: Use ${sourceType} directly to avoid state drift.`, STYLES.actionLabel);
|
|
215
429
|
} else {
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
430
|
+
const boolKeywords = ["is", "has", "can", "should", "loading", "success", "error", "active", "enabled", "open", "visible"];
|
|
431
|
+
const boolCount = clusterMetas.filter(
|
|
432
|
+
(c) => boolKeywords.some((kw) => c.name.toLowerCase().startsWith(kw))
|
|
433
|
+
).length;
|
|
434
|
+
const isBoolExplosion = cluster.length > 2 && boolCount / cluster.length > 0.5;
|
|
435
|
+
if (isBoolExplosion) {
|
|
436
|
+
console.log(`%cDiagnosis:%c Boolean Explosion. Multiple booleans updating in sync.`, STYLES.bold, "");
|
|
437
|
+
console.log(`%cSolution:%c Combine into a single %cstatus%c string or a %creducer%c.`, STYLES.actionLabel, "", STYLES.actionPill, "", STYLES.actionPill, "");
|
|
438
|
+
} else if (cluster.length > 2) {
|
|
439
|
+
console.log(`%cDiagnosis:%c Sibling Updates. These states respond to the same event.`, STYLES.bold, "");
|
|
440
|
+
console.log(`%cSolution:%c This may be intentional. If not, consolidate into a %creducer%c.`, STYLES.actionLabel, "", STYLES.actionPill, "");
|
|
220
441
|
} else {
|
|
221
442
|
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.
|
|
443
|
+
console.log(`%cSolution:%c Derive one from the other via %cuseMemo%c.`, STYLES.actionLabel, "", STYLES.actionPill, "");
|
|
223
444
|
}
|
|
224
445
|
}
|
|
225
446
|
console.groupEnd();
|
|
226
447
|
});
|
|
227
448
|
} else {
|
|
228
|
-
console.log("%c\u2728 Your architecture is clean. No redundant state detected.",
|
|
449
|
+
console.log("%c\u2728 Your architecture is clean. No redundant state detected.", `color: ${THEME.success}; font-weight: bold;`);
|
|
450
|
+
}
|
|
451
|
+
console.groupEnd();
|
|
452
|
+
};
|
|
453
|
+
var displayRedundancyAlert = (labelA, metaA, labelB, metaB, sim) => {
|
|
454
|
+
if (!isWeb || !shouldLog(`redundant-${labelA}-${labelB}`)) return;
|
|
455
|
+
const infoA = parseLabel2(labelA);
|
|
456
|
+
const infoB = parseLabel2(labelB);
|
|
457
|
+
const isContextMirror = metaA.role === "local" /* LOCAL */ && metaB.role === "context" /* CONTEXT */ || metaB.role === "local" /* LOCAL */ && metaA.role === "context" /* CONTEXT */;
|
|
458
|
+
const isStoreMirror = metaA.role === "local" /* LOCAL */ && metaB.role === "store" /* STORE */ || metaB.role === "local" /* LOCAL */ && metaA.role === "store" /* STORE */;
|
|
459
|
+
const alertType = isContextMirror ? "CONTEXT MIRRORING" : isStoreMirror ? "STORE MIRRORING" : "DUPLICATE STATE";
|
|
460
|
+
console.group(`%c \u264A BASIS | ${alertType} `, STYLES.headerProblem);
|
|
461
|
+
console.log(`%c\u{1F4CD} Location: %c${infoA.file}`, STYLES.bold, STYLES.location);
|
|
462
|
+
console.log(`%cIssue:%c ${infoA.name} and ${infoB.name} are synchronized (${(sim * 100).toFixed(0)}%).`, STYLES.bold, "");
|
|
463
|
+
if (isContextMirror || isStoreMirror) {
|
|
464
|
+
const sourceType = isStoreMirror ? "External Store" : "Global Context";
|
|
465
|
+
console.log(
|
|
466
|
+
`%cFix:%c Local state is 'shadowing' ${sourceType}. Delete the local state and consume the %c${sourceType}%c value directly.`,
|
|
467
|
+
STYLES.bold,
|
|
468
|
+
"",
|
|
469
|
+
STYLES.actionPill,
|
|
470
|
+
""
|
|
471
|
+
);
|
|
472
|
+
} else {
|
|
473
|
+
if (isBooleanLike(infoA.name) || isBooleanLike(infoB.name)) {
|
|
474
|
+
console.log(
|
|
475
|
+
`%cFix:%c Boolean Explosion detected. Merge flags into a single %cstatus%c string or %cuseReducer%c.`,
|
|
476
|
+
STYLES.bold,
|
|
477
|
+
"",
|
|
478
|
+
STYLES.actionPill,
|
|
479
|
+
"",
|
|
480
|
+
STYLES.actionPill,
|
|
481
|
+
""
|
|
482
|
+
);
|
|
483
|
+
} else {
|
|
484
|
+
console.log(
|
|
485
|
+
`%cFix:%c Redundant State detected. Derive %c${infoB.name}%c from %c${infoA.name}%c during render, or use %cuseMemo%c.`,
|
|
486
|
+
STYLES.bold,
|
|
487
|
+
"",
|
|
488
|
+
STYLES.label,
|
|
489
|
+
"",
|
|
490
|
+
STYLES.label,
|
|
491
|
+
"",
|
|
492
|
+
STYLES.actionPill,
|
|
493
|
+
""
|
|
494
|
+
);
|
|
495
|
+
}
|
|
229
496
|
}
|
|
230
497
|
console.groupEnd();
|
|
231
498
|
};
|
|
232
499
|
var displayCausalHint = (targetLabel, targetMeta, sourceLabel, sourceMeta) => {
|
|
233
500
|
if (!isWeb || !shouldLog(`causal-${sourceLabel}-${targetLabel}`)) return;
|
|
234
|
-
const target =
|
|
235
|
-
const source =
|
|
236
|
-
const
|
|
237
|
-
|
|
501
|
+
const target = parseLabel2(targetLabel);
|
|
502
|
+
const source = parseLabel2(sourceLabel);
|
|
503
|
+
const headerType = sourceMeta.role === "context" /* CONTEXT */ ? "CONTEXT SYNC LEAK" : sourceMeta.role === "store" /* STORE */ ? "STORE SYNC LEAK" : "DOUBLE RENDER";
|
|
504
|
+
const isEffect = sourceLabel.includes("effect") || sourceLabel.includes("useLayoutEffect");
|
|
505
|
+
console.groupCollapsed(`%c \u26A1 BASIS | ${headerType} `, STYLES.headerProblem);
|
|
238
506
|
console.log(`%c\u{1F4CD} Location: %c${target.file}`, STYLES.bold, STYLES.location);
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
console.log(
|
|
507
|
+
console.log(`%cIssue:%c ${source.name} triggers ${target.name} in separate frames.`, STYLES.bold, "");
|
|
508
|
+
if (isEffect) {
|
|
509
|
+
console.log(
|
|
510
|
+
`%cFix:%c Derive %c${target.name}%c during the render phase (remove effect) or wrap in %cuseMemo%c.`,
|
|
511
|
+
STYLES.bold,
|
|
512
|
+
"",
|
|
513
|
+
STYLES.label,
|
|
514
|
+
"",
|
|
515
|
+
STYLES.actionPill,
|
|
516
|
+
""
|
|
517
|
+
);
|
|
242
518
|
} else {
|
|
243
|
-
console.log(
|
|
519
|
+
console.log(
|
|
520
|
+
`%cFix:%c Merge %c${target.name}%c with %c${source.name}%c into a single state update.`,
|
|
521
|
+
STYLES.bold,
|
|
522
|
+
"",
|
|
523
|
+
STYLES.label,
|
|
524
|
+
"",
|
|
525
|
+
STYLES.label,
|
|
526
|
+
""
|
|
527
|
+
);
|
|
244
528
|
}
|
|
245
|
-
console.log(`%cFix:%c Derive %c${target.name}%c during the first render.`, STYLES.bold, STYLES.action, STYLES.label, "");
|
|
246
529
|
console.groupEnd();
|
|
247
530
|
};
|
|
248
531
|
var displayViolentBreaker = (label, count, threshold) => {
|
|
249
532
|
if (!isWeb) return;
|
|
250
533
|
const parts = label.split(" -> ");
|
|
251
|
-
console.group(`%c \u{1F6D1} BASIS CRITICAL | CIRCUIT BREAKER `,
|
|
534
|
+
console.group(`%c \u{1F6D1} BASIS CRITICAL | CIRCUIT BREAKER `, STYLES.headerProblem);
|
|
252
535
|
console.error(`INFINITE LOOP DETECTED
|
|
253
536
|
Variable: ${parts[1] || label}
|
|
254
537
|
Frequency: ${count} updates/sec`);
|
|
255
|
-
console.log(`%cACTION: Update BLOCKED to prevent browser freeze.`,
|
|
538
|
+
console.log(`%cACTION: Update BLOCKED to prevent browser freeze.`, `color: ${THEME.problem}; font-weight: bold;`);
|
|
256
539
|
console.groupEnd();
|
|
257
540
|
};
|
|
258
541
|
var displayBootLog = (windowSize) => {
|
|
259
542
|
if (!isWeb) return;
|
|
260
|
-
console.log(`%cBasis%cAuditor%c
|
|
543
|
+
console.log(`%cBasis%cAuditor%c "Graph Era" (Window: ${windowSize})`, STYLES.basis, STYLES.version, `color: ${THEME.muted}; font-style: italic; margin-left: 8px;`);
|
|
261
544
|
};
|
|
262
545
|
|
|
263
546
|
// src/core/constants.ts
|
|
@@ -267,10 +550,37 @@ var LOOP_THRESHOLD = 150;
|
|
|
267
550
|
var VOLATILITY_THRESHOLD = 25;
|
|
268
551
|
|
|
269
552
|
// src/core/analysis.ts
|
|
553
|
+
var CAUSAL_MARGIN = 0.05;
|
|
554
|
+
var isEventDriven = (label, graph) => {
|
|
555
|
+
for (const [parent, targets] of graph.entries()) {
|
|
556
|
+
if (parent.startsWith("Event_Tick_") && targets.has(label)) {
|
|
557
|
+
return true;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return false;
|
|
561
|
+
};
|
|
270
562
|
var calculateAllSimilarities = (entryA, entryB) => {
|
|
271
|
-
const sync = calculateSimilarityCircular(
|
|
272
|
-
|
|
273
|
-
|
|
563
|
+
const sync = calculateSimilarityCircular(
|
|
564
|
+
entryA.meta.buffer,
|
|
565
|
+
entryA.meta.head,
|
|
566
|
+
entryB.meta.buffer,
|
|
567
|
+
entryB.meta.head,
|
|
568
|
+
0
|
|
569
|
+
);
|
|
570
|
+
const bA = calculateSimilarityCircular(
|
|
571
|
+
entryA.meta.buffer,
|
|
572
|
+
entryA.meta.head,
|
|
573
|
+
entryB.meta.buffer,
|
|
574
|
+
entryB.meta.head,
|
|
575
|
+
1
|
|
576
|
+
);
|
|
577
|
+
const aB = calculateSimilarityCircular(
|
|
578
|
+
entryA.meta.buffer,
|
|
579
|
+
entryA.meta.head,
|
|
580
|
+
entryB.meta.buffer,
|
|
581
|
+
entryB.meta.head,
|
|
582
|
+
-1
|
|
583
|
+
);
|
|
274
584
|
return { sync, bA, aB, max: Math.max(sync, bA, aB) };
|
|
275
585
|
};
|
|
276
586
|
var shouldSkipComparison = (entryA, entryB, dirtyLabels2) => {
|
|
@@ -278,49 +588,79 @@ var shouldSkipComparison = (entryA, entryB, dirtyLabels2) => {
|
|
|
278
588
|
if (dirtyLabels2.has(entryB.label) && entryA.label > entryB.label) return true;
|
|
279
589
|
return false;
|
|
280
590
|
};
|
|
281
|
-
var
|
|
591
|
+
var pushViolation = (map, source, detail) => {
|
|
592
|
+
if (!map.has(source)) {
|
|
593
|
+
map.set(source, []);
|
|
594
|
+
}
|
|
595
|
+
const list = map.get(source);
|
|
596
|
+
const exists = list.some(
|
|
597
|
+
(v) => v.type === detail.type && v.target === detail.target
|
|
598
|
+
);
|
|
599
|
+
if (!exists) {
|
|
600
|
+
list.push(detail);
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
var isGlobalSource = (role) => role === "context" /* CONTEXT */ || role === "store" /* STORE */;
|
|
604
|
+
var detectRedundancy = (entryA, entryB, similarities, redundantSet, violationMap) => {
|
|
282
605
|
const roleA = entryA.meta.role;
|
|
283
606
|
const roleB = entryB.meta.role;
|
|
284
|
-
if (roleA
|
|
607
|
+
if (isGlobalSource(roleA) && isGlobalSource(roleB)) return;
|
|
285
608
|
if (entryA.meta.density < 2 || entryB.meta.density < 2) return;
|
|
286
|
-
if (roleA === "local" /* LOCAL */ && roleB
|
|
609
|
+
if (roleA === "local" /* LOCAL */ && isGlobalSource(roleB)) {
|
|
287
610
|
redundantSet.add(entryA.label);
|
|
611
|
+
pushViolation(violationMap, entryB.label, { type: "context_mirror", target: entryA.label, similarity: similarities.max });
|
|
288
612
|
displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta, similarities.max);
|
|
289
|
-
} else if (roleA
|
|
613
|
+
} else if (isGlobalSource(roleA) && roleB === "local" /* LOCAL */) {
|
|
290
614
|
redundantSet.add(entryB.label);
|
|
615
|
+
pushViolation(violationMap, entryA.label, { type: "context_mirror", target: entryB.label, similarity: similarities.max });
|
|
291
616
|
displayRedundancyAlert(entryB.label, entryB.meta, entryA.label, entryA.meta, similarities.max);
|
|
292
617
|
} else if (roleA === "local" /* LOCAL */ && roleB === "local" /* LOCAL */) {
|
|
293
618
|
redundantSet.add(entryA.label);
|
|
294
619
|
redundantSet.add(entryB.label);
|
|
620
|
+
pushViolation(violationMap, entryA.label, { type: "duplicate_state", target: entryB.label, similarity: similarities.max });
|
|
621
|
+
pushViolation(violationMap, entryB.label, { type: "duplicate_state", target: entryA.label, similarity: similarities.max });
|
|
295
622
|
displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta, similarities.max);
|
|
296
623
|
}
|
|
297
624
|
};
|
|
298
|
-
var detectCausalLeak = (entryA, entryB, similarities) => {
|
|
625
|
+
var detectCausalLeak = (entryA, entryB, similarities, violationMap, graph) => {
|
|
299
626
|
if (entryA.isVolatile || entryB.isVolatile) return;
|
|
627
|
+
if (similarities.max - similarities.sync < CAUSAL_MARGIN) return;
|
|
628
|
+
const addLeak = (source, target) => {
|
|
629
|
+
if (isEventDriven(target, graph)) return;
|
|
630
|
+
if (!violationMap.has(source)) {
|
|
631
|
+
violationMap.set(source, []);
|
|
632
|
+
}
|
|
633
|
+
violationMap.get(source).push({ type: "causal_leak", target });
|
|
634
|
+
const sourceEntry = source === entryA.label ? entryA : entryB;
|
|
635
|
+
const targetEntry = source === entryA.label ? entryB : entryA;
|
|
636
|
+
displayCausalHint(target, targetEntry.meta, source, sourceEntry.meta);
|
|
637
|
+
};
|
|
300
638
|
if (similarities.bA === similarities.max) {
|
|
301
|
-
|
|
639
|
+
addLeak(entryA.label, entryB.label);
|
|
302
640
|
} else if (similarities.aB === similarities.max) {
|
|
303
|
-
|
|
641
|
+
addLeak(entryB.label, entryA.label);
|
|
304
642
|
}
|
|
305
643
|
};
|
|
306
|
-
var detectSubspaceOverlap = (dirtyEntries, allEntries, redundantSet, dirtyLabels2) => {
|
|
644
|
+
var detectSubspaceOverlap = (dirtyEntries, allEntries, redundantSet, dirtyLabels2, graph) => {
|
|
307
645
|
let compCount = 0;
|
|
646
|
+
const violationMap = /* @__PURE__ */ new Map();
|
|
308
647
|
for (const entryA of dirtyEntries) {
|
|
309
648
|
for (const entryB of allEntries) {
|
|
310
649
|
if (shouldSkipComparison(entryA, entryB, dirtyLabels2)) continue;
|
|
311
650
|
compCount++;
|
|
312
651
|
const similarities = calculateAllSimilarities(entryA, entryB);
|
|
313
652
|
if (similarities.max > SIMILARITY_THRESHOLD) {
|
|
314
|
-
detectRedundancy(entryA, entryB, similarities, redundantSet);
|
|
315
|
-
detectCausalLeak(entryA, entryB, similarities);
|
|
653
|
+
detectRedundancy(entryA, entryB, similarities, redundantSet, violationMap);
|
|
654
|
+
detectCausalLeak(entryA, entryB, similarities, violationMap, graph);
|
|
316
655
|
}
|
|
317
656
|
}
|
|
318
657
|
}
|
|
319
|
-
return compCount;
|
|
658
|
+
return { compCount, violationMap };
|
|
320
659
|
};
|
|
321
660
|
|
|
322
661
|
// src/engine.ts
|
|
323
662
|
var BASIS_INSTANCE_KEY = /* @__PURE__ */ Symbol.for("__basis_engine_instance__");
|
|
663
|
+
var EVENT_TTL = 1e4;
|
|
324
664
|
var NULL_SIGNAL = {
|
|
325
665
|
role: "proj" /* PROJECTION */,
|
|
326
666
|
buffer: new Uint8Array(0),
|
|
@@ -328,6 +668,28 @@ var NULL_SIGNAL = {
|
|
|
328
668
|
density: 0,
|
|
329
669
|
options: {}
|
|
330
670
|
};
|
|
671
|
+
var activeEventId = null;
|
|
672
|
+
var pruneGraph = () => {
|
|
673
|
+
const now = Date.now();
|
|
674
|
+
for (const source of instance.graph.keys()) {
|
|
675
|
+
if (source.startsWith("Event_Tick_")) {
|
|
676
|
+
const parts = source.split("_");
|
|
677
|
+
const timestamp = parseInt(parts[2], 10);
|
|
678
|
+
if (now - timestamp > EVENT_TTL) {
|
|
679
|
+
instance.graph.delete(source);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
var getEventId = () => {
|
|
685
|
+
if (!activeEventId) {
|
|
686
|
+
activeEventId = `Event_Tick_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`;
|
|
687
|
+
requestAnimationFrame(() => {
|
|
688
|
+
activeEventId = null;
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
return activeEventId;
|
|
692
|
+
};
|
|
331
693
|
var getGlobalInstance = () => {
|
|
332
694
|
const root = globalThis;
|
|
333
695
|
if (!root[BASIS_INSTANCE_KEY]) {
|
|
@@ -340,7 +702,10 @@ var getGlobalInstance = () => {
|
|
|
340
702
|
tick: 0,
|
|
341
703
|
isBatching: false,
|
|
342
704
|
currentEffectSource: null,
|
|
705
|
+
lastStateUpdate: null,
|
|
343
706
|
pausedVariables: /* @__PURE__ */ new Set(),
|
|
707
|
+
graph: /* @__PURE__ */ new Map(),
|
|
708
|
+
violationMap: /* @__PURE__ */ new Map(),
|
|
344
709
|
metrics: {
|
|
345
710
|
lastAnalysisTimeMs: 0,
|
|
346
711
|
comparisonCount: 0,
|
|
@@ -370,6 +735,14 @@ var calculateTickEntropy = (tickIdx) => {
|
|
|
370
735
|
});
|
|
371
736
|
return 1 - activeCount / total;
|
|
372
737
|
};
|
|
738
|
+
var recordEdge = (source, target) => {
|
|
739
|
+
if (!source || !target || source === target) return;
|
|
740
|
+
if (!instance.graph.has(source)) {
|
|
741
|
+
instance.graph.set(source, /* @__PURE__ */ new Map());
|
|
742
|
+
}
|
|
743
|
+
const targets = instance.graph.get(source);
|
|
744
|
+
targets.set(target, (targets.get(target) || 0) + 1);
|
|
745
|
+
};
|
|
373
746
|
var analyzeBasis = () => {
|
|
374
747
|
if (!instance.config.debug || dirtyLabels.size === 0) {
|
|
375
748
|
return;
|
|
@@ -400,16 +773,33 @@ var analyzeBasis = () => {
|
|
|
400
773
|
nextRedundant.add(l);
|
|
401
774
|
}
|
|
402
775
|
});
|
|
403
|
-
const compCount = detectSubspaceOverlap(
|
|
776
|
+
const { compCount, violationMap } = detectSubspaceOverlap(
|
|
404
777
|
dirtyEntries,
|
|
405
778
|
allEntries,
|
|
406
779
|
nextRedundant,
|
|
407
|
-
snapshot
|
|
780
|
+
snapshot,
|
|
781
|
+
instance.graph
|
|
408
782
|
);
|
|
409
783
|
instance.redundantLabels.clear();
|
|
410
784
|
nextRedundant.forEach((l) => {
|
|
411
785
|
instance.redundantLabels.add(l);
|
|
412
786
|
});
|
|
787
|
+
violationMap.forEach((newList, label) => {
|
|
788
|
+
const existing = instance.violationMap.get(label) || [];
|
|
789
|
+
newList.forEach((detail) => {
|
|
790
|
+
const alreadyExists = existing.some(
|
|
791
|
+
(v) => v.type === detail.type && v.target === detail.target
|
|
792
|
+
);
|
|
793
|
+
if (!alreadyExists) {
|
|
794
|
+
existing.push(detail);
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
instance.violationMap.set(label, existing);
|
|
798
|
+
});
|
|
799
|
+
if (instance.violationMap.size > 500) {
|
|
800
|
+
const keys = Array.from(instance.violationMap.keys()).slice(0, 200);
|
|
801
|
+
keys.forEach((k) => instance.violationMap.delete(k));
|
|
802
|
+
}
|
|
413
803
|
instance.metrics.lastAnalysisTimeMs = performance.now() - analysisStart;
|
|
414
804
|
instance.metrics.comparisonCount = compCount;
|
|
415
805
|
instance.metrics.lastAnalysisTimestamp = Date.now();
|
|
@@ -431,6 +821,7 @@ var processHeartbeat = () => {
|
|
|
431
821
|
instance.currentTickBatch.clear();
|
|
432
822
|
currentTickRegistry = {};
|
|
433
823
|
instance.isBatching = false;
|
|
824
|
+
instance.lastStateUpdate = null;
|
|
434
825
|
if (dirtyLabels.size > 0) {
|
|
435
826
|
analyzeBasis();
|
|
436
827
|
}
|
|
@@ -442,6 +833,7 @@ var recordUpdate = (label) => {
|
|
|
442
833
|
if (now - instance.lastCleanup > 1e3) {
|
|
443
834
|
instance.loopCounters.clear();
|
|
444
835
|
instance.lastCleanup = now;
|
|
836
|
+
pruneGraph();
|
|
445
837
|
}
|
|
446
838
|
const count = (instance.loopCounters.get(label) || 0) + 1;
|
|
447
839
|
instance.loopCounters.set(label, count);
|
|
@@ -450,17 +842,25 @@ var recordUpdate = (label) => {
|
|
|
450
842
|
instance.pausedVariables.add(label);
|
|
451
843
|
return false;
|
|
452
844
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
845
|
+
const meta = instance.history.get(label);
|
|
846
|
+
let edgeSource = null;
|
|
847
|
+
if (instance.currentEffectSource) {
|
|
848
|
+
edgeSource = instance.currentEffectSource;
|
|
849
|
+
} else {
|
|
850
|
+
edgeSource = getEventId();
|
|
851
|
+
}
|
|
852
|
+
if (edgeSource && edgeSource !== label) {
|
|
853
|
+
recordEdge(edgeSource, label);
|
|
854
|
+
if (instance.currentEffectSource && instance.currentEffectSource !== label) {
|
|
855
|
+
const sourceMeta = instance.history.get(instance.currentEffectSource) || NULL_SIGNAL;
|
|
856
|
+
if (meta && sourceMeta && meta.density < VOLATILITY_THRESHOLD && sourceMeta.density < VOLATILITY_THRESHOLD) {
|
|
857
|
+
displayCausalHint(label, meta, instance.currentEffectSource, sourceMeta);
|
|
461
858
|
}
|
|
462
859
|
}
|
|
463
860
|
}
|
|
861
|
+
if (meta && meta.role === "local" /* LOCAL */ && !instance.currentEffectSource) {
|
|
862
|
+
instance.lastStateUpdate = label;
|
|
863
|
+
}
|
|
464
864
|
if (currentTickRegistry[label]) return true;
|
|
465
865
|
currentTickRegistry[label] = true;
|
|
466
866
|
instance.currentTickBatch.add(label);
|
|
@@ -479,7 +879,7 @@ var configureBasis = (c) => {
|
|
|
479
879
|
}
|
|
480
880
|
};
|
|
481
881
|
var registerVariable = (l, o = {}) => {
|
|
482
|
-
if (
|
|
882
|
+
if (o.suppressAll) return;
|
|
483
883
|
if (!instance.history.has(l)) {
|
|
484
884
|
instance.history.set(l, {
|
|
485
885
|
buffer: new Uint8Array(WINDOW_SIZE),
|
|
@@ -504,10 +904,10 @@ var endEffectTracking = () => {
|
|
|
504
904
|
};
|
|
505
905
|
var printBasisHealthReport = (threshold = 0.5) => {
|
|
506
906
|
if (!instance.config.debug) return;
|
|
507
|
-
displayHealthReport(instance.history, threshold);
|
|
907
|
+
displayHealthReport(instance.history, threshold, instance.violationMap);
|
|
508
908
|
};
|
|
509
909
|
var getBasisMetrics = () => ({
|
|
510
|
-
engine: "v0.
|
|
910
|
+
engine: "v0.6.x",
|
|
511
911
|
hooks: instance.history.size,
|
|
512
912
|
analysis_ms: instance.metrics.lastAnalysisTimeMs.toFixed(3),
|
|
513
913
|
entropy: instance.metrics.systemEntropy.toFixed(3)
|
|
@@ -721,7 +1121,14 @@ var BasisHUD = () => {
|
|
|
721
1121
|
if (!canvas) return;
|
|
722
1122
|
const ctx = canvas.getContext("2d");
|
|
723
1123
|
if (!ctx) return;
|
|
724
|
-
const entries = Array.from(history.entries())
|
|
1124
|
+
const entries = Array.from(history.entries()).sort((a, b) => {
|
|
1125
|
+
const roleOrder = (role) => {
|
|
1126
|
+
if (role === "context") return 0;
|
|
1127
|
+
if (role === "store") return 1;
|
|
1128
|
+
return 2;
|
|
1129
|
+
};
|
|
1130
|
+
return roleOrder(a[1].role) - roleOrder(b[1].role);
|
|
1131
|
+
});
|
|
725
1132
|
const dpr = window.devicePixelRatio || 1;
|
|
726
1133
|
const rawWidth = HUD_DIMENSIONS.WINDOW_SIZE * HUD_DIMENSIONS.COL_WIDTH + HUD_DIMENSIONS.LABEL_WIDTH + HUD_DIMENSIONS.PADDING * 2;
|
|
727
1134
|
const rawHeight = Math.max(entries.length * HUD_DIMENSIONS.ROW_HEIGHT + HUD_DIMENSIONS.PADDING * 2, 80);
|
|
@@ -777,7 +1184,7 @@ function renderMatrix(ctx, entries) {
|
|
|
777
1184
|
let rowIndex = 0;
|
|
778
1185
|
for (const [label, meta] of entries) {
|
|
779
1186
|
const y = rowIndex * rowH + pad;
|
|
780
|
-
const isContext = meta.role === "context";
|
|
1187
|
+
const isContext = meta.role === "context" || meta.role === "store";
|
|
781
1188
|
const isRedundant = !isContext && redundantLabels.has(label);
|
|
782
1189
|
const { buffer, head } = meta;
|
|
783
1190
|
let uiPos = 0;
|
|
@@ -796,7 +1203,8 @@ function renderMatrix(ctx, entries) {
|
|
|
796
1203
|
const stateName = label.split(" -> ")[1] || label;
|
|
797
1204
|
const textX = L * colW + pad + 10;
|
|
798
1205
|
ctx.fillStyle = isContext ? HUD_THEME.header : isRedundant ? HUD_THEME.error : HUD_THEME.text;
|
|
799
|
-
|
|
1206
|
+
const prefix = meta.role === "store" ? "\u03A3 " : isContext ? "\u03A9 " : isRedundant ? "! " : "";
|
|
1207
|
+
ctx.fillText(prefix + stateName, textX, y + 9);
|
|
800
1208
|
rowIndex++;
|
|
801
1209
|
}
|
|
802
1210
|
ctx.fillStyle = HUD_THEME.grid;
|
|
@@ -825,7 +1233,7 @@ var HUDHeader = ({ isExpanded }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)
|
|
|
825
1233
|
},
|
|
826
1234
|
children: [
|
|
827
1235
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: isExpanded ? "STATE BASIS MATRIX" : "\u{1F4D0} BASIS ACTIVE" }),
|
|
828
|
-
isExpanded && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { opacity: 0.8, fontSize: "9px" }, children: "v0.
|
|
1236
|
+
isExpanded && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { opacity: 0.8, fontSize: "9px" }, children: "v0.6.x" })
|
|
829
1237
|
]
|
|
830
1238
|
}
|
|
831
1239
|
);
|