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

@neanic/dashboard-vue

v0.7.1

Published

A reusable, ZED-inspired Vue 3 admin shell — installed as a plugin.

Readme

@neanic/dashboard-vue

A reusable, ZED-editor-inspired admin shell for Vue 3, installed as a plugin. It gives you a complete application frame — top bar, dockable side panels, a command palette, a customizable widget dashboard, theming and a settings system — and you fill it with self-contained feature modules. The backend is pluggable behind one transport-agnostic interface, so the same app runs against an in-process mock, HTTP+SSE or a WebSocket with no module changes.

import { createApp } from 'vue'
import { createDashboard } from '@neanic/dashboard-vue'
import '@neanic/dashboard-vue/style.css'
import App from './App.vue'

createApp(App).use(createDashboard()).mount('#app')

That one call gives you a running admin app. Everything below is how to make it yours.

Highlights

  • Plugin install — one app.use(createDashboard(config)), configured by a single typed object.
  • Module system — a feature is one object: it contributes routes, navigation, dockable panels, commands, settings sections and dashboard widgets. The shell wires them in; no shell code changes.
  • Backend-agnostic — modules code against DashboardClient; swap the transport (mock / HTTP+SSE / WebSocket / your own) in one line.
  • Built-in surfaces — command palette (⌘K), preferences (⌘,), a customizable widget dashboard, movable/resizable docks, dark/light/contrast themes, a responsive compact layout.
  • Embeddable — mount the whole shell under any base path (e.g. /admin) inside a larger app.
  • Ships compiled CSS and full TypeScript declarations.

Installation

bun add @neanic/dashboard-vue
# peer dependencies — your app provides the shared copies:
bun add vue vue-router @tanstack/vue-query
# optional — the examples use Lucide icons; `icon` fields accept any component:
bun add @lucide/vue

@neanic/dashboard-vue requires Vue 3, Vue Router and @tanstack/vue-query as peer dependencies (it uses TanStack Query for module data fetching). npm/pnpm work the same way.

Quick start

A consuming app needs three things: a root component that renders <RouterView />, the plugin install, and the stylesheet.

<!-- App.vue — the root component -->
<template>
  <RouterView />
</template>
// main.ts
import { createApp } from 'vue'
import { createDashboard, createMockClient } from '@neanic/dashboard-vue'
import '@neanic/dashboard-vue/style.css'
import App from './App.vue'

createApp(App)
  .use(createDashboard({ client: createMockClient([]) }))
  .mount('#app')

createDashboard registers the shell as a route layout and installs the router, so the root component only renders <RouterView />. Out of the box you get:

  • a top bar and status bar,
  • the Navigator panel (left dock),
  • the command palette (⌘K / Ctrl+K) and Preferences (⌘,),
  • the /dashboard route — a customizable widget canvas (/ redirects to it),
  • the /settings route,
  • dark / light / contrast themes with a configurable accent.

Configuration — createDashboard(config)

interface DashboardConfig {
  /** Feature modules mounted on top of the built-in core. Default: none. */
  modules?: AppModule[]
  /** The backend adapter. Default: an empty in-process mock. */
  client?: DashboardClient
  /** An existing router to register the shell's routes on. Default: created. */
  router?: Router
  /** Path the shell mounts under — set e.g. `/admin` to embed. Default: `/`. */
  basePath?: string
  /** App identity — the name shown in the top bar. */
  branding?: { name?: string }
  /** Theme defaults and which themes the user may choose. */
  theme?: {
    /** Theme used until the user picks one. Default: `'dark'`. */
    default?: 'dark' | 'light' | 'contrast'
    /** Accent color used until the user picks one. */
    accent?: string
    /** Themes offered in Appearance settings. Default: all. */
    available?: ('dark' | 'light' | 'contrast')[]
  }
  /** Optional built-in features, all off by default. */
  features?: {
    /** The Notes scratchpad panel in the right dock. Default: `false`. */
    notesPanel?: boolean
  }
  /** The optional command queue — enabled by supplying a transport. */
  commandQueue?: { transport: CommandTransport }
}

Every field is optional — createDashboard() with no arguments is valid.

  • modules — your feature modules (see below). The built-in core, dashboard and settings modules are always included.
  • client — how the app reaches its backend (see Backends).
  • router — pass your app's existing Router and the shell adds its routes to it; omit it and the shell creates one.
  • basePath — mount the shell under a sub-path. With basePath: '/admin' the dashboard lives at /admin, /admin/dashboard, … — useful for embedding the admin area inside a larger site. Nav links are prefixed automatically.
  • branding.name — the app name shown in the top bar (defaults to Dashboard).
  • themedefault and accent seed the look until the user changes it in Preferences; available restricts the themes Appearance offers. A user's saved choice always wins over default/accent.
  • features — switches for optional built-ins the shell ships but does not enable. notesPanel: true adds a Notes scratchpad panel to the right dock (saved to the browser's localStorage).
  • commandQueue — supply a CommandTransport and the shell adds a Commands panel where queued write operations run through your transport and report their outcome (see Command queue).

Feature modules

A module is a plain object describing what a feature contributes. Build one with defineModule (an identity helper that gives you full type-checking):

// modules/reports/index.ts
import { defineModule } from '@neanic/dashboard-vue'
import { FileBarChart } from '@lucide/vue'
import ReportsView from './ReportsView.vue'
import ReportsWidget from './ReportsWidget.vue'

export const reportsModule = defineModule({
  id: 'reports',
  title: 'Reports',
  order: 20,
  routes: [
    { path: '/reports', name: 'reports', component: ReportsView,
      meta: { title: 'Reports' } },
  ],
  nav: [
    { id: 'reports', label: 'Reports', to: '/reports', icon: FileBarChart,
      order: 20 },
  ],
  widgets: [
    { id: 'reports.summary', title: 'Reports summary', component: ReportsWidget,
      defaultWidth: 2 },
  ],
})
app.use(createDashboard({ modules: [reportsModule] }))

What a module can contribute

interface AppModule {
  id: string
  title: string
  order?: number               // lower registers first
  routes?: RouteRecordRaw[]     // Vue Router routes
  nav?: NavItem[]
  panels?: PanelDef[]
  commands?: Command[]
  settings?: SettingsSection[]
  widgets?: WidgetDef[]
}

| Contribution | Surfaces as | Key fields | |---|---|---| | routes | center content, URL-addressable | standard Vue Router RouteRecordRaw | | nav | entries in the Navigator panel | { id, label, to, icon?, order?, group? } | | panels | dockable side panels | { id, title, component, icon?, defaultSide?, defaultOpen?, order? } | | commands | command palette + keyboard shortcuts | { id, title, run, icon?, shortcut? } | | settings | Preferences modal or /settings | { id, title, scope, component, icon?, order? } | | widgets | tiles on the /dashboard canvas | { id, title, component, icon?, defaultWidth?, order? } |

  • nav items with the same group render together under that heading.
  • panels defaultSide is 'left' or 'right'; users can move and resize them at runtime.
  • commands shortcut uses mod for ⌘/Ctrl, e.g. 'mod+shift+k'.
  • settings scope is 'user' (renders in the Preferences modal) or 'workspace' (renders in the /settings route).
  • widgets defaultWidth is a 1–4 column span; users add, reorder and resize widgets and the layout persists to localStorage.

Every type is exported from @neanic/dashboard-vue for explicit annotation (AppModule, NavItem, PanelDef, Command, SettingsSection, WidgetDef, …).

Backends

Modules never talk to a transport directly — they use a DashboardClient:

interface DashboardClient {
  initialize(): Promise<BackendManifest>
  query<T>(module: string, op: string, params?: unknown): Promise<T>
  subscribe<T>(module: string, topic: string, handler: (data: T) => void): Unsubscribe
}

Inside a module, reach it with useDashboardClient():

import { useDashboardClient } from '@neanic/dashboard-vue'

const client = useDashboardClient()
const rows = await client.query<Report[]>('reports', 'list')
const stop = client.subscribe<Report[]>('reports', 'feed', (rows) => { /* … */ })

Pick the transport with config.client — three adapters ship with the package:

import {
  createMockClient,      // in-process, no wire — great for development
  createHttpClient,      // HTTP for queries + Server-Sent Events for subscriptions
  createWebSocketClient, // one duplex socket
} from '@neanic/dashboard-vue'

createDashboard({ client: createHttpClient({ baseUrl: 'https://api.example.com' }) })
createDashboard({ client: createWebSocketClient({ url: 'wss://api.example.com/ws' }) })

The mock adapter is assembled from per-module in-process backends — the fastest way to develop against realistic data:

import { createMockClient, type MockBackendModule } from '@neanic/dashboard-vue'

const reportsBackend: MockBackendModule = {
  manifest: { id: 'reports', version: 1, queries: ['list'], subscriptions: ['feed'] },
  query: (op) => (op === 'list' ? loadReports() : Promise.reject({ code: 'not_found', message: op })),
  subscribe: (topic, emit) => { /* … */ return () => {} },
}

createDashboard({ client: createMockClient([reportsBackend]) })

Any backend that speaks the Dashboard Protocol works unchanged — a Node/Bun service, a Rust crate, a .NET app. The wire contract, the SSE/WebSocket formats, and how to write your own adapter are documented in docs/backend-adapters.md. A complete reference backend (HTTP + WebSocket, TypeScript) lives in examples/reference-server/.

Command queue

An optional panel where write operations (saves, deletes, CQRS commands) queue up, run asynchronously, and report their outcome — with operation name, payload, queue/settle timestamps and a success or failure message per entry.

The shell owns the queue and the UI; you own the wire. Enabling it means implementing one interface and passing it to createDashboard:

import type { CommandTransport } from '@neanic/dashboard-vue'

const transport: CommandTransport = {
  async dispatch(command, report) {
    // REST style — settle as soon as the request round-trips:
    const res = await fetch(`/api/v1/${command.name}`, {
      method: 'POST',
      body: JSON.stringify(command.payload),
    })
    if (res.ok) report.complete('Saved.')
    else report.fail(`HTTP ${res.status}`)
  },
}

app.use(createDashboard({ commandQueue: { transport } }))

Settlement is decoupled from dispatch, so async backends work the same way — send now, settle whenever the result arrives:

// CQRS over WebSockets — every command goes to one endpoint, results
// arrive later as frames correlated by command id.
const pending = new Map<string, CommandReporter>()

socket.onmessage = (msg) => {
  const frame = JSON.parse(msg.data)
  const report = pending.get(frame.commandId)
  if (!report) return
  frame.ok ? report.complete(frame.message) : report.fail(frame.message)
  pending.delete(frame.commandId)
}

const transport: CommandTransport = {
  dispatch(command, report) {
    pending.set(command.id, report)
    socket.send(JSON.stringify({ type: '_command', ...command }))
  },
}

report.complete() / report.fail() are first-call-wins; a rejected promise or a synchronous throw from dispatch also settles the command as failed.

Modules enqueue work from anywhere. The optional third argument carries presentation extras — a human-readable title the Commands panel shows instead of the raw operation name:

import { useCommandQueueStore } from '@neanic/dashboard-vue'

const queue = useCommandQueueStore()
queue.enqueue('car.save', { id: 42, model: 'GT' })
queue.enqueue('car.save', { id: 42, model: 'GT' }, { title: 'Save car #42' })

Each entry shows the queue time plus status and backend processing time (max two decimals): a live pending for 0.42s while in flight, then completed in 1.42s: Success / completed in 1.42s: Error once the transport reports (14:03:12 · completed in 1.42s: Success). The message line below is transport-supplied and only rendered when present — report complete() without a message unless there is something worth saying.

Command history

The list is session-only by default. Pass a history adapter to keep it across reloads — load() runs once at startup, save() gets the full list after every change. The built-in adapter uses localStorage:

import { localStorageCommandHistory } from '@neanic/dashboard-vue'

app.use(
    createDashboard({
        commandQueue: {
            transport,
            history: localStorageCommandHistory(), // key/limit configurable
        },
    }),
)

The seam is transport-agnostic on purpose: once your app has login, swap in a server-backed adapter that loads the user's history from your API and persists changes there — same interface, per-user across machines:

const serverHistory: CommandHistoryAdapter = {
    load: () => fetch('/api/me/commands').then((r) => r.json()),
    save: (items) =>
        fetch('/api/me/commands', {
            method: 'PUT',
            body: JSON.stringify(items),
        }),
}

Restored commands that never settled (the page closed while they were in flight) come back as failed with an "interrupted" message — retryable, and never stuck showing Pending. Server-backed adapters that know the real outcome should return entries already settled.

Deliberately out of scope: retries beyond the panel's manual Retry button, dedup/ordering guarantees, and offline persistence of the queue. Those are project policy — wrap your transport if you need them.

Built-in modules

Always included, regardless of config:

  • core — the Navigator panel, the command palette, the Preferences modal, dock-toggle commands, and the Appearance / Shortcuts / System settings sections. The Notes scratchpad panel joins when features.notesPanel is on.
  • dashboard — the /dashboard route: a customizable widget canvas. Widgets come from any module's widgets contribution.
  • settings — the /settings route: hosts every workspace-scoped settings section.

Included only when configured:

  • commandQueue — the Commands panel (right dock), present when a commandQueue.transport is supplied.

Theming

Three themes ship — dark, light and contrast — plus an orthogonal accent hue. Both are user preferences (set in the Preferences modal), persisted to localStorage and applied before first paint. The package ships one compiled stylesheet (@neanic/dashboard-vue/style.css); import it once.

TypeScript

The package is written in TypeScript and ships .d.ts declarations. All public types — DashboardConfig, AppModule and the contribution types, DashboardClient and the protocol frames — are exported from the entry point.


Working on the library itself

This repository is both the library and its dev harness.

src/                  the library — published as @neanic/dashboard-vue
  core/               module types, registry, router, built-in modules,
                      protocol + client adapters
  components/         the shell — TopBar, StatusBar, Dock, OverlayHost, …
  stores/             shell state — plain reactive composables
  styles/app.pcss     the single stylesheet (Tailwind v4 tokens + @apply)
  index.ts            the public entry point
  install.ts          createDashboard()
playground/           a faithful consumer app used to develop the library
examples/
  reference-server/   a TypeScript backend speaking the Dashboard Protocol
docs/                 backend-adapters.md, library-plan.md
bun run dev        # run the playground (the dev app)
bun run build      # type-check + build the library into dist/
bun run typecheck  # type-check everything
bun run server     # run the reference backend on :8787

The playground defaults to the in-process mock; VITE_BACKEND=http bun run dev or VITE_BACKEND=ws bun run dev point it at the reference server instead.