bini-overlay

Official

Development overlay for Bini.js: an animated status badge, route inspector, and full-screen error panel with source-mapped stack traces.

Overview

bini-overlay is a Vite plugin bundle that replaces Vite's default vite-error-overlay with a polished development experience:

  • A floating badge that animates during HMR updates, shows the current route type, and opens a preferences menu.
  • A red issue pill that replaces the badge when something breaks.
  • A full-screen error panel with a syntax-highlighted code frame, source-mapped call stack, and one-click "open in editor".

Everything is registered with apply: 'serve'. Nothing is injected into production builds.

Features

Status badge

Animated logo

SVG stroke-drawing animation on page load and every HMR update.

Shadow DOM

Renders inside a shadow root, so it never collides with your app's CSS.

Issue pill

Morphs into a red 1 Issue / N Issues pill when errors are present.

Live route type

Static, Dynamic, or Not Found - updates on client-side navigation.

Route Info inspector

Opens a matched-route tree, including layouts and the page file.

Persistent preferences

Theme, corner position, size, and a recordable visibility shortcut.

Error panel

Captures everything

Runtime errors, unhandled rejections, Vite build/transform errors, and errors reported by your error boundaries.

Labelled by type

Runtime Error, Parse Error, Build Error, Type Error, Unhandled Rejection.

Shiki code frame

Five lines of context read from disk, highlighted with Shiki (dark-plus).

Source-mapped stack

Frames resolve back to your original source when a source map is available.

Open in editor

Click any frame to jump to the exact line in code, cursor, zed, and friends.

Smart dedupe

Duplicate errors merge, compile errors sort first, cascade errors hide while a real error exists.

Auto-clears on fix

No manual refresh - HMR delivers a fix and the panel closes.

Graceful fallback

Plain unhighlighted text if Shiki can't load.

Installation

>_Terminal
$ npm install bini-overlay --save-dev
vite >= 8 is a required peer dependency. To enable route type detection and the Route Info inspector, also install the optional peer dependency bini-router >= 2.0.0 - Bini.js projects already include it.

Quick Start

vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { biniOverlay } from 'bini-overlay'

export default defineConfig({
  plugins: [react(), biniOverlay()],
})

Options

bini-overlay options
interface BiniOverlayOptions {
  /**
   * App directory scanned for routes. Used to resolve the current page's
   * route type and to power the Route Info inspector.
   * Must match the `appDir` you pass to `biniroute()` if customised.
   * @default 'src/app'
   */
  appDir?: string

  /**
   * Hide the loading badge and its menu while keeping the error overlay.
   * @default false
   */
  disableBadge?: boolean

  /**
   * Editor binary used by "open in editor". When omitted, the first of
   * `code`, `cursor`, `zed`, `subl`, `webstorm` found on PATH is used.
   */
  editor?: string
}

Example:

vite.config.ts
biniOverlay({
  appDir: 'src/app',
  editor: 'cursor',
})

The Vite base config is picked up automatically for route matching, so no separate base-path option is needed. Code-frame highlighting always uses Shiki's dark-plus theme and is not configurable.

The Badge

By default the badge sits in the bottom-left corner.

StateAppearanceBehaviour
LoadingLogo draws itself with a stroke animationRuns on page load and on each HMR update
IdleFilled gradient logoDefault state when there are no errors
ErrorRed pill showing 1 Issue / N IssuesClick the count to open the error panel, or the logo to open the menu

Menu

Click the badge to open the menu.

ItemDescription
IssuesShown only when errors exist. Reopens the error panel.
RouteCurrent route type: Static, Dynamic, or Not Found.
BundlerDisplays the active bundler (Rolldown).
Route InfoOpens the route inspector (see below).
PreferencesOpens the preferences popover.

Route Info

Shows the matched route path as a tree, including the layout files and page file that render it. Dynamic (:param) and catch-all (*) segments are tagged with a chip. Unmatched URLs show a "renders your 404 page" message. Requires bini-router >= 2.0.0.

Preferences

SettingOptionsDefault
ThemeSystem, Light, DarkSystem
PositionBottom Left, Bottom Right, Top Left, Top RightBottom Left
SizeSmall, Medium, LargeMedium
Hide for this sessionHides the badge until the tab is closedOff
ShortcutRecord any key combination to toggle visibilityAlt+B

Preferences are stored in localStorage under bini-overlay:prefs. The session-hide flag lives in sessionStorage and is cleared when the tab closes.

The Error Panel

When an error occurs, the panel opens automatically.

SectionDescription
HeaderError type, file:line chip, copy button, and close button
MessageCleaned error message, with the originating plugin shown for build errors
Code FrameFive lines of context read from disk, with the failing line marked by >>> and a red row highlight
Call StackApplication frames first, framework frames collapsed behind a "N framework frames hidden" toggle
Component StackReact component hierarchy, when provided by an error boundary
NavigationPrev/Next arrows and counter when multiple errors are queued

Code frame example

src/components/Greeting.tsx
    10: function Greeting() {
>>> 11:   const name = user.name
    12:   return <h1>Hello, {name}!</h1>
    13: }

Error lifecycle

Lifecycle
1. Error occurs   -> badge becomes a red pill and the panel opens
2. Multiple errors -> navigate with prev/next; duplicates are merged
3. You fix it      -> HMR update arrives, resolved errors are cleared
4. All clear       -> panel closes and the badge returns to idle

HMR events

EventBehaviour
vite:errorAdds the error, shows the pill, opens the panel
vite:beforeUpdateRemoves errors belonging to the updated modules and starts the loading animation
vite:afterUpdateClears remaining errors, closes the panel, and returns the badge to idle

Unrecoverable errors

If the app has crashed so completely that nothing is rendered, or an error carries a component stack, the close button is hidden. The panel stays up until a successful HMR update recovers the page, so you never end up staring at a blank screen.

Reporting Errors From Your App

Errors thrown at runtime and unhandled rejections are captured automatically. To route errors from a React error boundary into the overlay, dispatch a __bini_error__ event:

ErrorBoundary.tsx
componentDidCatch(error: Error, info: React.ErrorInfo) {
  window.dispatchEvent(
    new CustomEvent('__bini_error__', {
      detail: {
        name: error.name,
        message: error.message,
        stack: error.stack,
        componentStack: info.componentStack,
        type: 'runtime',
      },
    }),
  )
}

The overlay dispatches a __bini_clear_errors__ event on window after every successful HMR update, so a boundary can listen for it to reset itself:

ErrorBoundary.tsx
useEffect(() => {
  const reset = () => setHasError(false)
  window.addEventListener('__bini_clear_errors__', reset)
  return () => window.removeEventListener('__bini_clear_errors__', reset)
}, [])

detail fields

FieldTypeDescription
messagestringError message
namestringError name (default Runtime Error)
stackstringStack trace, source-mapped when possible
componentStackstringOptional React component stack
file / linestring / numberOptional; inferred from the stack when omitted
typestringDefaults to runtime

Dev Server Endpoints

The plugins register the following middleware on the Vite dev server. They exist only during vite dev.

EndpointQueryPurpose
/__bini_code_contextfile, lineReturns the surrounding lines for a code frame. Files are cached by modification time (up to 64 entries).
/__bini_sourcemapfile, line, columnMaps a transformed position back to the original source via the module graph.
/__bini_open_editorfile, lineOpens a file at a line in your editor.
/__bini_route_matchpathReturns static, dynamic, or not_found for a URL.
/__bini_route_infopathReturns matched segments, layouts, and page file for the Route Info inspector.

The route manifest is built lazily from appDir and invalidated automatically when files under it are added, changed, or removed.

Supported editors: code, cursor, zed, subl, webstorm. Pass the editor option to force a specific binary.

Security

The overlay exposes file-reading and process-launching endpoints, so they are locked down:

  • Same-origin only. Requests are checked via Sec-Fetch-Site, falling back to an Origin/Host comparison. Cross-origin requests receive 403.
  • Project-root confinement. Code-context and open-in-editor paths are resolved and rejected if they escape the current working directory.
  • Dev server only. Every plugin uses apply: 'serve'; none run in vite build.

Architecture

biniOverlay() returns seven cooperating plugins:

PluginRole
bini-overlay:code-contextServes code frames from disk
bini-overlay:sourcemapResolves source-mapped stack positions
bini-overlay:open-editorLaunches your editor at a file and line
bini-overlay:routesRoute matching and route info via bini-router
bini-overlay:vite-interceptNeutralises Vite's built-in vite-error-overlay element
bini-overlay:errorClient-side error capture and the error panel
bini-overlay:loadingBadge, menu, route info, and preferences

Requirements

Version

ToolVersion
Node.js>= 18.0.0
Vite>= 8.0.0 (Rolldown-based)

Dependencies

PackageTypePurpose
vite (>= 8.0.0)Peer, requiredHost build tool and dev server. Vite 7 and earlier are not supported.
bini-router (>= 2.0.0)Peer, optionalRoute type and Route Info in the badge menu. Without it, everything else still works.
@jridgewell/trace-mappingDependencyResolves source-mapped stack frames (installed automatically).
Network access: syntax highlighting loads Shiki from esm.sh, and the badge UI loads Inter and JetBrains Mono from Google Fonts. Offline, the overlay still works with plain text and system fonts.

Troubleshooting

  • The overlay doesn't appear - confirm biniOverlay() is in plugins and that you're running vite dev, not a production build.
  • Code frames have no colours - Shiki failed to load from esm.sh. Check network access; the overlay falls back to plain text.
  • Route type / Route Info shows Not Found everywhere - bini-router isn't installed, or appDir doesn't match the value passed to biniroute().
  • Stack frames point at transformed code - no source map is available for that file. Enable source maps in your Vite config.
  • Clicking a stack frame does nothing - no supported editor was found on PATH. Pass the editor option to force a binary.
  • 403 Forbidden from /__bini_* endpoints - the request came from a different origin. Open the app in a browser at the dev server's hostname rather than through a proxy.
  • The badge is missing - it may be hidden for the session. Check the preferences, or clear the session-hide flag by closing the tab.
  • The overlay stays open after a fix - wait for the HMR update to land. If it doesn't, the error may not belong to a module Vite can invalidate; reload the page.
Was this helpful?