Skip to content

The Forensic Catalog

lpetronika edited this page Feb 16, 2026 · 3 revisions

The Forensic Catalog: Interpreting Alerts (v0.6)

Basis uses Graph Theory and Signal Processing to find architectural debt. Unlike linters, it doesn't look at code syntax. It looks at the topology of state updates in real-time.

Below are the patterns Basis detects, ranked by severity.


1. Global Event Fragmentation (Prime Mover)

Signal: A single external trigger (click, timer, fetch) causes simultaneous updates to multiple independent state roots ($E \rightarrow \lbrace A, B, C \rbrace$).

Why it matters: The application state is fragmented. Because these updates hit different roots (often different Contexts or files), React may tear the UI or trigger waterfalls.

❌ Problematic Pattern

// Inside a component or event handler
const handleLogin = () => {
  setUser(userData);      // Root 1 (AuthContext)
  setTheme('dark');       // Root 2 (ThemeContext)
  setLastLogin(Date.now); // Root 3 (UserMetaContext)
}

✅ Solution: Atomic State

Consolidate these variables into a single Store or Reducer so the transition happens as one atomic update.

🖥️ Console Output (Health Report)

🎯 REFACTOR PRIORITIES (PRIME MOVERS)
1 ⚡ Global Event (handleLogin) (AuthContext.tsx)
Global Sync Event: An external trigger is updating 3 roots simultaneously. Occurred 12 times.
Impacts: AuthContext.tsx (user) + ThemeContext.tsx (theme) + UserMeta.tsx (lastLogin)
Solution: These variables update together but live in different hooks/files. Consolidate them into a single useReducer or atomic store update.


2. Double Render (Sync Leaks)

Signal: State A updates, triggers an Effect, which immediately updates State B in the next frame ($A \rightarrow \text{Effect} \rightarrow B$).

Why it matters: You're forcing the browser to paint, run logic, then immediately repaint. This causes layout shifts and wastes CPU cycles.

❌ Problematic Pattern

const [data, setData] = useState(null);
const [isValid, setIsValid] = useState(false);

useEffect(() => {
  setIsValid(check(data)); // ⚡ Triggers a second render
}, [data]);

✅ Solution: Derived State

const [data, setData] = useState(null);
// Calculate during render phase (zero cost)
const isValid = check(data); 

🖥️ Console Output

⚡ BASIS | DOUBLE RENDER
📍 Location: ValidationForm.tsx
Issue: effect_L45 triggers isValid in a separate frame.
Fix: Derive isValid during the render phase (remove effect) or wrap in useMemo.


3. Duplicate State (Redundancy)

Signal: Two local variables update in perfect unison ($\tau = 0$) with a similarity of 1.0.

Why it matters: You're storing the same information twice. This breaks the "Single Source of Truth" principle.

❌ Problematic Pattern

const [firstName, setFirst] = useState('John');
const [fullName, setFull] = useState('John Doe'); // Redundant

✅ Solution: Derivation

const [firstName, setFirst] = useState('John');
const fullName = `${firstName} Doe`;

🖥️ Console Output

♊ BASIS | DUPLICATE STATE
📍 Location: UserProfile.tsx
Issue: firstName and fullName are synchronized (100%).
Fix: Redundant State detected. Derive fullName from firstName during render, or use useMemo.


4. Boolean Explosion

Signal: A subtype of Duplicate State where 3+ boolean flags toggle in a synchronized pattern.

Why it matters: You've built an implicit state machine with "impossible states" (e.g., isLoading: true AND isSuccess: true at the same time).

❌ Problematic Pattern

const [isLoading, setLoading] = useState(false);
const [isSuccess, setSuccess] = useState(false);
const [hasError, setError] = useState(false);

✅ Solution: Finite State Machine

// One variable, zero impossible states
const [status, setStatus] = useState('idle' | 'loading' | 'success' | 'error');

🖥️ Console Output

♊ BASIS | DUPLICATE STATE
Fix: Boolean Explosion detected. Merge flags into a single status string or useReducer.


5. Context Mirroring

Signal: A local state variable perfectly mirrors a Context value.

Why it matters: You've created a local copy of a global truth. If the context updates and your local state doesn't catch it (or the other way around), you get bugs. This is state drift.

❌ Problematic Pattern

const { user } = useContext(AuthContext);
const [localName, setLocalName] = useState(user.name); // 🚩 Shadow copy

useEffect(() => {
  setLocalName(user.name); // Manual sync
}, [user]);

✅ Solution

Use the context value directly in your JSX. Delete the local copy.

🖥️ Console Output

♊ BASIS | CONTEXT MIRRORING
Issue: localName and AuthContext are synchronized (100%).
Fix: Local state is 'shadowing' Global Context. Delete the local state and consume the Context value directly.


6. Circuit Breaker (Infinite Loops)

Signal: A state variable updates >150 times per second.

Why it matters: This is a critical logic error. Most likely a useEffect that updates its own dependency. Basis kills the update to save your browser tab.

🖥️ Console Output

🛑 BASIS CRITICAL | CIRCUIT BREAKER
INFINITE LOOP DETECTED
Variable: count | Frequency: 151 updates/sec.
ACTION: Update BLOCKED to prevent browser freeze.


Stream vs. Report: When they disagree

You'll see two types of output from Basis. When they seem to contradict each other, here's how to think about it.

1. The Live Stream (Tactical)

  • What it does: Flags issues the moment they happen. Works with local pattern matching ($A \leftrightarrow B$).
  • Limitation: It assumes variables are local. It doesn't know about file boundaries.
  • Example: It sees user and theme changing together. It suggests: "Merge these via useMemo."

2. The Health Report (Strategic)

  • What it does: Analyzes the entire causal chain after the fact. Works with the full graph topology ($Event \rightarrow \lbrace A, B \rbrace$).
  • Advantage: It knows file paths and context boundaries.
  • Example: It sees user (in AuthContext.tsx) and theme (in ThemeContext.tsx) triggered by the same event. It knows useMemo is impossible across files. It overrules the Stream and suggests: "Consolidate into a Global Store/Reducer."

The rule

If the Stream and the Report disagree, trust the Report. The Report has the full topological context that the real-time stream doesn't have. The Stream finds the symptom. The Report finds the root cause.

Clone this wiki locally