npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dashcat

v0.0.1-alpha.1

Published

a minimal, beautiful gui for tweaking values

Readme

# npm coming soon! for now, install from github:
> npm install isaac-mason/dashcat

dashcat

dashcat is a minimal, beautiful gui for tweaking values and watching data — vanilla, functional, dark, and monospace. It's a lil-gui / Leva-style control panel with a dockable tab/split layout, first-class mathcat shapes, and zero runtime dependencies.

Table Of Contents

Overview

  • You own the state. dashcat instruments objects, stores, and engine state you already have — it reads and writes them in place, it never owns them.
  • Explicit, with smart defaults. One verb, add(target, key, options). The widget is auto-detected from the value; a control option overrides it and unlocks the mathcat shapes.
  • Tweak and watch. Sliders, colors, vectors, and rotations for editing; monitors and live graphs (with auto-scaling units) for observing.
  • Dockable. Panels are tabs — drag to reorder, split into new regions, collapse, and close/reopen. No fixed single panel.
  • Functional & tiny. No classes, no framework, one injected scoped stylesheet, and a few KB gzipped.

Quick start

import { dashboard } from 'dashcat';

const state = { speed: 1, label: 'hello', enabled: true, quality: 'high' };

const dash = dashboard();
const panel = dash.panel({ title: 'settings' });

panel.add(state, 'speed', { min: 0, max: 10 }); // number + range → slider
panel.add(state, 'label'); // string → text
panel.add(state, 'enabled'); // boolean → toggle
panel.add(state, 'quality', { options: ['low', 'medium', 'high'] }); // → select
panel.button('reset', () => console.log('clicked')); // an action button

The widget is chosen from the value's type: number → slider/number, boolean → toggle, string → text, number[] → vector, and options → select.

Binding

add binds an object property, a get/set accessor (for nested / derived state), or — for the read-only watch controls — a plain getter:

// bind an object property (the common case)
panel.add(state, 'speed', { min: 0, max: 10 });

// or a get/set accessor — nested, derived, or unit-converted state
const camera = { fov: 60 };
panel.add({ get: () => camera.fov, set: (v) => (camera.fov = v) }, { min: 20, max: 90, label: 'fov' });

// or a getter only — read-only, for the watch controls
panel.monitor(() => performance.now());

Controls

Ambiguous values take an explicit control. A number[3] might be a vector or a color; a string might be text or a hex color:

// the widget is auto-detected from the value. ambiguous ones — a `number[]` could be a
// vector or a color, a string could be text or a hex color — name the `control`:
const material = { tint: [0.9, 0.2, 0.1] };
panel.add(material, 'tint', { control: 'color' });

Built-in control names: number, slider, text, toggle, select, color, vec2, vec3, vec4, spherical, euler, quaternion, interval. Prop-less helpers: button, buttonGroup, html, element.

Every add returns a chainable handle:

panel
    .add(state, 'speed', { min: 0, max: 10 })
    .onChange((v) => console.log('speed', v))
    .onFinishChange((v) => console.log('done', v))
    .listen(); // reflect external changes to state.speed

mathcat shapes

mathcat's shapes are plain number tuples (Vec3 = [x, y, z], Quat = [x, y, z, w], Color = [r, g, b]), so dashcat edits them in place with no adapter. Pass a list of editors to flip between them:

// mathcat shapes are plain number tuples; dashcat reads & writes them in place
const transform = { position: [0, 1, 0], rotation: [0, 0, 0, 1] };

panel.add(transform, 'position', { control: 'vec3', step: 0.1 });
panel.add(transform, 'rotation', { control: ['euler', 'quaternion'] }); // flip between editors

Watching data — monitors & graphs

Read-only controls that poll a getter (throttled with interval). unit picks a smart auto-scaling formatter (bytes, duration, si, %) or a literal suffix, applied to the value and a graph's min/avg/max. Off-screen and inactive-tab charts pause automatically.

const stats = { fps: 60, memory: 1.4e9, frame: 16.6 };

panel.monitor(() => stats.fps, { unit: 'fps' }); //  → "58 fps"
panel.monitor(() => stats.memory, { unit: 'bytes' }); //  → "1.4 GB"
panel.graph(() => stats.frame, { unit: 'duration', min: 0, max: 33 }); //  → "16.6 ms" + min/avg/max

Folders & filter

const lighting = panel.folder('lighting');
lighting.add(state, 'speed', { min: 0, max: 1, label: 'intensity' });

panel.filter(); // a search box that hides controls whose label doesn't match

Dependent & optional controls

show and disable accept predicates that read your state directly (no keyed store). optional adds a toggle that nulls the value when off:

const opts = { mode: 'basic', detail: 0.5, shadows: true, bias: 0.02, maxDistance: 100 as number | null };

// `show` / `disable` take predicates that read your state directly
panel.add(opts, 'detail', { show: () => opts.mode === 'advanced' });
panel.add(opts, 'bias', { min: 0, max: 0.1, step: 0.001, disable: () => !opts.shadows });

// `optional` adds a checkbox that toggles the value on/off (→ null)
panel.add(opts, 'maxDistance', { min: 0, max: 500, optional: true });

Custom controls

A control is just a Control<T> — a function that builds its dom from base and returns the handle. Pass it as control; it mounts exactly like a built-in, with no registration:

import { base, type Control, el, on } from 'dashcat';

// a 5-star rating — a Control<number> built with the same `base` the built-ins use
const rating: Control<number> = (ctx, prop) => {
    const b = base<number>(ctx, prop, prop.name ?? 'rating');
    const stars = [0, 1, 2, 3, 4].map((i) => {
        const star = el('button', 'dc-button', { type: 'button', textContent: '★' });
        b.onDispose(on(star, 'click', () => b.handle.set(i + 1)));
        return star;
    });
    b.controlEl.append(...stars);
    b.render(() => stars.forEach((s, i) => (s.style.opacity = prop.get() > i ? '1' : '0.3')));
    return b.handle;
};

panel.add(state, 'speed', { control: rating }); // mounts exactly like a built-in

Panels, tabs & docking

// panels are tabs in a dockable layout: drag a tab to reorder, drop it on an
// edge to split into a new region, collapse a group, or close (stash) & reopen.
const perf = dash.panel({ title: 'perf', dock: 'bottom' });
perf.graph(() => stats.fps, { unit: 'fps', min: 0, max: 120 });

Theming

Everything is scoped under .dashcat and driven by CSS custom properties — override any of them to retheme:

.dashcat {
  --dc-surface: #17191e;   --dc-surface-muted: #202329;
  --dc-fg: #e7e8ea;        --dc-fg-muted: #8b9096;
  --dc-border: #3a3f47;    --dc-accent: #2b5fd9;
  --dc-font: ui-monospace, monospace;  --dc-size: 11px;
}

API

dashboard

/** create a dockable dashboard — the host for panels, tabs, and splits. */
export function dashboard(opts: DashboardOptions = {}): Dashboard;

add options

/** options for `add`. the widget is auto-detected from the value; `control` overrides it. */
export type AddOptions<T> = {
    label?: string;
    hint?: string;
    listen?: boolean;
    show?: boolean | (() => boolean);
    disable?: boolean | (() => boolean);
    onChange?: (value: T) => void;
    onFinishChange?: (value: T) => void;
    /** fires once when an edit begins; `onEditEnd` is an alias of `onFinishChange`. */
    onEditStart?: (value: T) => void;
    onEditEnd?: (value: T) => void;
    /** show a checkbox that toggles the value on/off (sets it to `null` when off). needs a non-null initial value. */
    optional?: boolean;
    /** explicit ordering within the panel/folder (lower first; default insertion order). */
    order?: number;
    /** override the widget: a built-in name, a custom `Control<T>`, or a list → a flip switch. */
    control?: ControlName<T> | Control<T> | Array<ControlName<T> | Control<T>>;
    // widget config — each control reads what it needs
    min?: number;
    max?: number;
    step?: number;
    options?: readonly T[] | Record<string, T>;
    space?: 'linear' | 'srgb';
    format?: (value: T) => string;
};

Control handle

/** the live handle returned when a control is mounted. everything chains. */
export type Handle<T> = {
    readonly row: HTMLElement;
    get(): T;
    set(value: T, finished?: boolean): Handle<T>;
    onChange(fn: (value: T) => void): Handle<T>;
    onFinishChange(fn: (value: T) => void): Handle<T>;
    /** fires once when an edit begins (drag start / first change of an interaction). */
    onEditStart(fn: (value: T) => void): Handle<T>;
    name(label: string): Handle<T>;
    hint(text: string): Handle<T>;
    listen(enable?: boolean): Handle<T>;
    show(visible: boolean | (() => boolean)): Handle<T>;
    disable(disabled?: boolean | (() => boolean)): Handle<T>;
    /** flip the active editor of a switchable control (no-op otherwise). */
    view(which: string | number): Handle<T>;
    refresh(): Handle<T>;
    destroy(): void;
};

Authoring a control

/**
 * a control is a widget for a `Prop<T>`. it's a plain factory: given a context
 * and a prop, it builds its dom and returns a live handle. built-in and custom
 * controls are the same shape, so they mount identically.
 */
export type Control<T> = (ctx: Context, prop: Prop<T>) => Handle<T>;
// a `prop` is a reference to state you already own — dashcat instruments it, it
// never owns it. three not-owned shapes: object+key, a get/set lens, or a
// getter-only source (read-only, for visibility). reactivity is a property of
// the binding: a `subscribe` means push updates, its absence means opt into
// `.listen()` polling.
export type Prop<T> = {
    get(): T;
    /** absent ⇒ read-only (a monitor/graph source). */
    set?(value: T): void;
    /** present ⇒ reactive; the control subscribes instead of polling. */
    subscribe?(onChange: () => void): () => void;
    /** display name, used as the default control label. filled in by the obj+key adapter. */
    name?: string;
};