bini-overlay
OfficialDevelopment 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
$ 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
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { biniOverlay } from 'bini-overlay'
export default defineConfig({
plugins: [react(), biniOverlay()],
})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:
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.
| State | Appearance | Behaviour |
|---|---|---|
| Loading | Logo draws itself with a stroke animation | Runs on page load and on each HMR update |
| Idle | Filled gradient logo | Default state when there are no errors |
| Error | Red pill showing 1 Issue / N Issues | Click the count to open the error panel, or the logo to open the menu |
Menu
Click the badge to open the menu.
| Item | Description |
|---|---|
| Issues | Shown only when errors exist. Reopens the error panel. |
| Route | Current route type: Static, Dynamic, or Not Found. |
| Bundler | Displays the active bundler (Rolldown). |
| Route Info | Opens the route inspector (see below). |
| Preferences | Opens 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
| Setting | Options | Default |
|---|---|---|
| Theme | System, Light, Dark | System |
| Position | Bottom Left, Bottom Right, Top Left, Top Right | Bottom Left |
| Size | Small, Medium, Large | Medium |
| Hide for this session | Hides the badge until the tab is closed | Off |
| Shortcut | Record any key combination to toggle visibility | Alt+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.
| Section | Description |
|---|---|
| Header | Error type, file:line chip, copy button, and close button |
| Message | Cleaned error message, with the originating plugin shown for build errors |
| Code Frame | Five lines of context read from disk, with the failing line marked by >>> and a red row highlight |
| Call Stack | Application frames first, framework frames collapsed behind a "N framework frames hidden" toggle |
| Component Stack | React component hierarchy, when provided by an error boundary |
| Navigation | Prev/Next arrows and counter when multiple errors are queued |
Code frame example
10: function Greeting() {
>>> 11: const name = user.name
12: return <h1>Hello, {name}!</h1>
13: }Error 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 idleHMR events
| Event | Behaviour |
|---|---|
| vite:error | Adds the error, shows the pill, opens the panel |
| vite:beforeUpdate | Removes errors belonging to the updated modules and starts the loading animation |
| vite:afterUpdate | Clears 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:
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:
useEffect(() => {
const reset = () => setHasError(false)
window.addEventListener('__bini_clear_errors__', reset)
return () => window.removeEventListener('__bini_clear_errors__', reset)
}, [])detail fields
| Field | Type | Description |
|---|---|---|
| message | string | Error message |
| name | string | Error name (default Runtime Error) |
| stack | string | Stack trace, source-mapped when possible |
| componentStack | string | Optional React component stack |
| file / line | string / number | Optional; inferred from the stack when omitted |
| type | string | Defaults to runtime |
Dev Server Endpoints
The plugins register the following middleware on the Vite dev server. They exist only during vite dev.
| Endpoint | Query | Purpose |
|---|---|---|
| /__bini_code_context | file, line | Returns the surrounding lines for a code frame. Files are cached by modification time (up to 64 entries). |
| /__bini_sourcemap | file, line, column | Maps a transformed position back to the original source via the module graph. |
| /__bini_open_editor | file, line | Opens a file at a line in your editor. |
| /__bini_route_match | path | Returns static, dynamic, or not_found for a URL. |
| /__bini_route_info | path | Returns 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 anOrigin/Hostcomparison. 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 invite build.
Architecture
biniOverlay() returns seven cooperating plugins:
| Plugin | Role |
|---|---|
| bini-overlay:code-context | Serves code frames from disk |
| bini-overlay:sourcemap | Resolves source-mapped stack positions |
| bini-overlay:open-editor | Launches your editor at a file and line |
| bini-overlay:routes | Route matching and route info via bini-router |
| bini-overlay:vite-intercept | Neutralises Vite's built-in vite-error-overlay element |
| bini-overlay:error | Client-side error capture and the error panel |
| bini-overlay:loading | Badge, menu, route info, and preferences |
Requirements
Version
| Tool | Version |
|---|---|
| Node.js | >= 18.0.0 |
| Vite | >= 8.0.0 (Rolldown-based) |
Dependencies
| Package | Type | Purpose |
|---|---|---|
| vite (>= 8.0.0) | Peer, required | Host build tool and dev server. Vite 7 and earlier are not supported. |
| bini-router (>= 2.0.0) | Peer, optional | Route type and Route Info in the badge menu. Without it, everything else still works. |
| @jridgewell/trace-mapping | Dependency | Resolves source-mapped stack frames (installed automatically). |
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 inpluginsand that you're runningvite 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-routerisn't installed, orappDirdoesn't match the value passed tobiniroute(). - 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
editoroption 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.