Spoosh

Devtool

Visual debugging panel for Spoosh

The devtool plugin provides a visual debugging panel that shows every request, plugin step, and cached state in your browser.

Installation

npm install @spoosh/devtool

Usage

import { Spoosh } from "@spoosh/core";
import { devtool } from "@spoosh/devtool";

const spoosh = new Spoosh<ApiSchema, Error>("/api").use([
  devtool(),
  // other plugins...
]);

A floating icon appears in the corner. Click it to open the panel.

Features

FeatureDescription
Request TimelineSee every request with timing, status, and duration
Plugin StepsWatch middleware execution order with before/after diffs
State InspectorBrowse cache entries, subscriber counts, refetch or delete
Event LogView invalidations, refetch triggers, custom plugin events
Status BadgesPending, success, error, stale, fresh indicators
Filter & SearchFilter by operation type, search by path or query key
Keyboard ShortcutsNavigate with keyboard (Esc, arrows, Ctrl+K, etc.)
Resizable PanelDrag to resize sidebar and split panels
Theme SwitchingDark and light mode
Position ConfigButton position (corners) and sidebar position (right/left/bottom)
Sensitive HeadersToggle to reveal/hide auth headers with eye icon
Export/ImportSave traces as JSON, import for analysis
SettingsMax history size, auto-follow, show/hide passed plugins

Options

devtool({
  enabled: true,
  showFloatingIcon: true,
  containerId: "devtool-container",
  sensitiveHeaders: ["authorization", "cookie", "x-api-key"],
});
OptionTypeDefaultDescription
enabledbooleantrueEnable or disable the devtool
showFloatingIconbooleantrueShow floating icon in corner
containerIdstringundefinedDOM element ID to render panel inside
sensitiveHeadersstring[]["authorization", "cookie", "x-api-key", ...]Headers to redact in UI and exports

Container Mode

By default, the devtool renders as a floating overlay. Use containerId to render the panel inside a specific DOM element instead, enabling side-by-side layouts where the app and devtool panel don't overlap.

Setup

  1. Add a container element to your HTML:
<!-- index.html -->
<body>
  <div id="root"></div>
  <div id="devtool-container"></div>
</body>
  1. Configure the devtool with the container ID:
devtool({
  enabled: import.meta.env.DEV,
  containerId: "devtool-container",
});
  1. Add CSS for side-by-side layout:
body {
  height: 100vh;
  display: flex;
  overflow: hidden;
}

#root {
  flex: 1;
  min-width: 0;
  overflow-y: auto;
}

#devtool-container {
  flex-shrink: 0;
  height: 100vh;
  overflow: hidden;
}

Behavior

In container mode:

  • The floating icon still appears and toggles the panel visibility
  • The panel renders inside the specified container element
  • App and devtool have separate scroll areas
  • Sidebar position setting is hidden (panel always renders in the container)
  • Panel width is resizable by dragging the left edge

Programmatic API

Access devtool functions through create():

import { create } from "@spoosh/react";

const { useRead, devtools } = create(spoosh);

// Toggle panel visibility
devtools.toggle();

// Clear all traces
devtools.clearTraces();

// Export traces as JSON
const traces = devtools.exportTraces();

// Show/hide floating icon
devtools.toggleFloatingIcon();
MethodDescription
toggle()Open or close the panel
clearTraces()Clear all recorded traces
exportTraces()Get traces as JSON array
toggleFloatingIcon()Show or hide the floating icon

Keyboard Shortcuts

ShortcutAction
EscClose panel
Ctrl/Cmd + KFocus search
/ Navigate items
1 / 2 / 3Switch views
Ctrl/Cmd + EExport traces
Ctrl/Cmd + LClear traces

Production

The plugin automatically disables itself when:

  • enabled: false is set
  • Running on server (SSR)
devtool({
  enabled: process.env.NODE_ENV === "development",
});

Plugin Tracing

When devtool is enabled, plugins can emit trace events to show what they're doing. See Tracing for full documentation.

function myPlugin(): SpooshPlugin {
  return {
    name: "my-app:my-plugin",
    operations: ["read"],

    middleware: async (context, next) => {
      const t = context.tracer?.("my-app:my-plugin");

      t?.log("Checking cache");
      t?.return("Cache hit");
      t?.skip("Already in progress");

      return next();
    },

    setup(context) {
      const et = context.eventTracer?.("my-app:my-plugin");

      et?.emit("Plugin initialized");
    },
  };
}

On this page