> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getlimelight.io/llms.txt
> Use this file to discover all available pages before exploring further.

# State Inspection

> Debug Zustand and Redux state in real-time with zero configuration

# State Inspection

Limelight's state inspector lets you view, track, and debug your application state in real-time. Connect your Zustand or Redux stores with a single line of code and get full visibility into every state change.

## Quick Start

Add your stores to the `Limelight.connect()` call:

```typescript theme={null}
import { Limelight } from "@getlimelight/sdk";
import { useUserStore } from "./stores/user";
import { useCartStore } from "./stores/cart";

Limelight.connect({
  stores: {
    user: useUserStore,
    cart: useCartStore,
  },
});
```

That's it. Open Limelight and you'll see every state change as it happens.

## Zustand

Limelight works with Zustand out of the box. Pass your store hooks directly:

```typescript theme={null}
import { create } from "zustand";

// Your existing store - no changes needed
const useUserStore = create((set) => ({
  user: null,
  isLoading: false,
  login: (user) => set({ user, isLoading: false }),
  logout: () => set({ user: null }),
}));

// Connect to Limelight
Limelight.connect({
  stores: {
    user: useUserStore,
  },
});
```

### Vanilla Stores

If you're using Zustand's vanilla stores (created with `createStore` instead of `create`), they work the same way:

```typescript theme={null}
import { createStore } from "zustand/vanilla";

const userStore = createStore((set) => ({
  user: null,
  login: (user) => set({ user }),
}));

Limelight.connect({
  stores: {
    user: userStore,
  },
});
```

### Action Names

Limelight automatically infers action names from your code. When you call a function like `login()` that internally calls `set()`, Limelight captures "login" as the action name.

```typescript theme={null}
const useUserStore = create((set) => ({
  user: null,
  // Limelight will show this as "login" in the timeline
  login: (user) => set({ user }),
  // This will show as "logout"
  logout: () => set({ user: null }),
}));
```

<Note>
  Action names are inferred from the call stack. If Limelight can't determine
  the name, it falls back to "set".
</Note>

## Redux

Connect your Redux store the same way:

```typescript theme={null}
import { configureStore } from "@reduxjs/toolkit";
import userReducer from "./slices/user";
import cartReducer from "./slices/cart";

const store = configureStore({
  reducer: {
    user: userReducer,
    cart: cartReducer,
  },
});

Limelight.connect({
  stores: {
    redux: store,
  },
});
```

Limelight automatically captures Redux action types and payloads:

```typescript theme={null}
// Dispatching this action...
dispatch(setUser({ name: "John", email: "john@example.com" }));

// ...shows up in Limelight as:
Action: "user/setUser"
Payload: { name: "John", email: "john@example.com" }
```

## Multiple Stores

You can connect as many stores as you need. Mix Zustand and Redux in the same app:

```typescript theme={null}
import { useAuthStore } from "./stores/auth";
import { useCartStore } from "./stores/cart";
import { useUIStore } from "./stores/ui";
import { legacyReduxStore } from "./redux/store";

Limelight.connect({
  stores: {
    auth: useAuthStore,
    cart: useCartStore,
    ui: useUIStore,
    legacy: legacyReduxStore,
  },
});
```

<Tip>
  Use descriptive names for your stores. These names appear in the Limelight UI
  and help you quickly identify which store an action belongs to.
</Tip>

## Configuration Options

### Disable State Inspection

If you want to connect stores but temporarily disable state inspection:

```typescript theme={null}
Limelight.connect({
  stores: {
    user: useUserStore,
  },
  enableStateInspector: false, // Stores are registered but not tracked
});
```

### Filter Sensitive Data

Use the `beforeSend` hook to filter or modify state before it's sent to Limelight:

```typescript theme={null}
Limelight.connect({
  stores: {
    user: useUserStore,
  },
  beforeSend: (event) => {
    // Filter out state updates from specific stores
    if (event.phase === "STATE:UPDATE" && event.data.storeId === "sensitive") {
      return null; // Don't send this event
    }

    // Redact sensitive fields
    if (event.phase === "STATE:UPDATE" && event.data.storeId === "user") {
      const state = { ...event.data.state };
      if (state.password) state.password = "[REDACTED]";
      if (state.token) state.token = "[REDACTED]";
      event.data.state = state;
    }

    return event;
  },
});
```

### Throttle High-Frequency Updates

For stores that update very frequently (e.g., mouse position, animations), you may want to filter updates:

```typescript theme={null}
let lastMouseUpdate = 0;

Limelight.connect({
  stores: {
    mouse: useMouseStore,
    ui: useUIStore,
  },
  beforeSend: (event) => {
    // Throttle mouse store updates to once per second
    if (event.phase === "STATE:UPDATE" && event.data.storeId === "mouse") {
      const now = Date.now();
      if (now - lastMouseUpdate < 1000) {
        return null;
      }
      lastMouseUpdate = now;
    }
    return event;
  },
});
```

## What Gets Captured

For each state change, Limelight captures:

| Field           | Description                                                                             |
| --------------- | --------------------------------------------------------------------------------------- |
| **Action Type** | The name of the function that triggered the change (Zustand) or the action type (Redux) |
| **Payload**     | The data passed to the action (Redux) or the partial state passed to `set()` (Zustand)  |
| **State**       | The full state after the change                                                         |
| **Diff**        | Which keys changed and their before/after values                                        |
| **Timestamp**   | When the change occurred                                                                |
| **Stack Trace** | Where in your code the change originated                                                |

## Supported Libraries

<CardGroup cols={2}>
  <Card title="Zustand" icon="bolt">
    Full support for `create()` and `createStore()`. Action names inferred
    automatically.
  </Card>

  <Card title="Redux" icon="layer-group">
    Full support for Redux Toolkit and vanilla Redux. Captures action types and
    payloads.
  </Card>
</CardGroup>

<Note>
  Support for Jotai, Recoil, and MobX is coming soon. [Let us
  know](https://github.com/getlimelight/limelight-sdk/issues) which libraries
  you'd like to see supported.
</Note>

## Best Practices

<AccordionGroup>
  <Accordion title="Use descriptive store names">
    The names you pass to `stores` appear throughout the Limelight UI. Use names that clearly describe what the store contains:

    ```typescript theme={null}
    // ✅ Good
    stores: {
      auth: useAuthStore,
      shoppingCart: useCartStore,
      userPreferences: usePreferencesStore,
    }

    // ❌ Avoid
    stores: {
      store1: useAuthStore,
      s2: useCartStore,
      data: usePreferencesStore,
    }
    ```
  </Accordion>

  <Accordion title="Name your Zustand actions">
    Limelight infers action names from your function names. Use clear, descriptive names:

    ```typescript theme={null}
    // ✅ Good - shows as "addItem" in Limelight
    const useCartStore = create((set) => ({
      items: [],
      addItem: (item) => set((s) => ({ items: [...s.items, item] })),
    }));

    // ❌ Avoid - shows as "set" in Limelight
    const useCartStore = create((set) => ({
      items: [],
      update: (item) => set((s) => ({ items: [...s.items, item] })),
    }));
    ```
  </Accordion>

  <Accordion title="Filter high-frequency updates">
    Stores that update many times per second can flood the timeline. Use
    `beforeSend` to throttle or filter these updates.
  </Accordion>

  <Accordion title="Redact sensitive data">
    Never send passwords, tokens, or PII to Limelight. Use `beforeSend` to redact sensitive fields before they leave the device.
  </Accordion>
</AccordionGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Store not appearing in Limelight">
    1. Make sure `Limelight.connect()` is called **after** your store is created
    2. Verify the store is passed correctly to the `stores` object
    3. Check that `enableStateInspector` is not set to `false`
    4. Look for errors in the console starting with `[Limelight]`
  </Accordion>

  <Accordion title="Actions showing as 'set' instead of function name">
    Limelight infers action names from the call stack. If your bundler minifies
    function names in development, the names may not be captured correctly.
    **Solutions:** - Ensure your development build doesn't minify function names -
    Use named functions instead of arrow functions for actions
  </Accordion>

  <Accordion title="State updates not appearing">
    1. Verify the WebSocket connection is established (check for "Connected"
       status) 2. Make sure you're not filtering out the events with `beforeSend` 3.
       Check that the state is actually changing (Limelight only captures changes)
  </Accordion>

  <Accordion title="Performance impact">
    Limelight is designed to have minimal performance impact:

    * State is captured synchronously but sent asynchronously
    * Diffs are computed on the desktop app, not in your app
    * The SDK automatically disables itself in production builds

    If you notice performance issues with high-frequency stores, use `beforeSend` to throttle updates.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Network Inspection" icon="globe" href="/features/network">
    Debug API requests alongside state changes
  </Card>

  <Card title="Console Logs" icon="terminal" href="/features/console">
    View console output with full stack traces
  </Card>
</CardGroup>

\`\`\` \`\`\`\`
