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

@hoardodile/workbench

v0.1.13

Published

Offline dev workbench for hoardodile content plugins.

Readme

@hoardodile/workbench

Offline dev workbench for hoardodile content plugins: mounts one plugin iframe against the offline mock host (@hoardodile/host-web), fed with real data by the dev server — no hoardodile server needed. The workbench page itself is a small React app built on @hoardodile/ui (the same design system and theme tokens the app and plugins use).

You normally never touch this package directly. hoardodile plugin dev (resolved from the plugin's own @hoardodile/workbench devDependency) watch-builds a plugin, captures its server-side hook results from the real worker sandbox, renders preview variants and video frames with the production pipeline, and serves the workbench at http://127.0.0.1:5199. If that port is already in use, the server rebinds to the next free port (and prints the real URL after it) instead of aborting, so a stale workbench never blocks a new one.

Terminal dev tooling (MIT) — like @hoardodile/host, it is not part of the SDK closure and never enters a shipped plugin bundle.

Page

The chrome strip shows the plugin name, the resource list, the hook status line and the current viewport. With multiple resources the list is a left sidebar (w-sidebar, dense single-line rows) at the sidebar breakpoint and collapses to a horizontal chip row in the toolbar below it; a single resource renders one row/chip. The plugin iframe floats as the design system's card surface on the canvas.

Iframe settings

The Configure popover edits every iframe configuration item, and their defaults are the main app's hardcoded defaults (so the workbench shows the plugin exactly as it ships):

| Setting | Default | App source | | --- | --- | --- | | Theme mode | System (follows the OS) | ThemeProvider defaultTheme | | Palette | Mono | ThemeProvider defaultPalette | | Icon style | Duotone | IconStyleProvider defaultStyle | | Language | System (navigator.language, "en" fallback) | app i18n detection | | Font family | Empty (app system stack) | the app's unset font pref | | Viewport | Fill (the app preview surface) | the preview dialog |

The workbench chrome itself is localized in the same five official languages (en/zh/ja/de/es): the chosen language drives both the chrome and the languageChanged push to the mounted iframe. Shared option names (palette/icon style/language) come from the app catalogs; the workbench's own copy lives in the workbench namespace of @hoardodile/i18n.

Manual acceptance after an i18n change (hoardodile plugin dev): switch through all five languages and check that (1) the workbench chrome follows immediately, (2) the plugin iframe switches without a reload, (3) plugin strings the bundle lacks fall back to English.

Theme, palette, icon style, language and font changes are pushed to the mounted iframe (theme/fonts/language pushes) without a reload — the same protocol the app's theme broadcast uses. A plugin built against a vanilla SDK that never subscribed to those pushes keeps its initial context; the Reload button re-posts the context with the current values. Element-level iframe attributes (sandbox, referrer policy, fullscreen, title) and the injected viewport meta stay fixed at the app's values.

Settings persist in localStorage under a workbench-only key, so they survive a plugin dev restart.

Serve entry

@hoardodile/workbench exports serveWorkbench(opts), an HTTP server that serves the prebuilt workbench page plus the plugin's built dist/ at /plugin. Everything about the resources arrives through provider callbacks, which is what keeps the serve entry dependency-free while still reaching real data (the React UI is prebuilt and inlined in the published dist/):

| Provider | Feeds | | --- | --- | | resources() | the resource list (sidebar / toolbar chips) | | files | /data reads and the plugin file URL shape | | snapshot(resId) | sandboxed detect / sourceMeta / searchMeta / listFiles / coverLocal / imageHashes | | state(resId) | seeds the mock host with the resource's comments, danmaku, prefs and cache | | preview(resId, path) | ?size=preview variants | | frame(resId, path, timeMs) | video seek-preview thumbnails |

hoardodile plugin dev supplies all of them. Omit one and the matching capability degrades honestly: without preview the original bytes are served, without frame the route stays unmounted, and the page's status line says so.

Run standalone against a plain directory when you only need the client side:

node dist/serve.mjs --plugin ./dist --data ./testdata --port 5199

--data serves one resource (the directory itself). To switch between many items from one folder, use --resource-dir — every direct subfolder becomes a resource, named by its basename, and the page lists them in the resource list:

node dist/serve.mjs --plugin ./dist --resource-dir ./testdata --port 5199

Both are the directory shape of the same resources()/files providers; --data keeps its single-resource meaning, --resource-dir is the consolidated "folder of many resources" shape.

Routes

GET /plugin/*                                  built plugin bundle
GET /data/<path>[?res=]                        raw entry bytes
GET /data/?list=1[&res=]                       entry names
GET /data/?stat=<path>[&res=]                  entry size
GET /api/workbench/resources                   resource picker list
GET /api/workbench/context?res=<id>            hooks + seeded state
GET /api/resources/:id/files/:token/*          plugin file URLs
GET /api/resources/:id/frame/:token/:name/:ms  video seek frame

The routing lives in one module (scripts/mounts.mjs) shared by the vite dev server and the published standalone server, so a route can never exist in only one of them.