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

@vellumai/web

v0.12.6

Published

Pre-built web SPA for the Vellum Assistant

Readme

clients/web

Vite + React Router v7 SPA for the Vellum assistant web app (chat, settings, library, docs).

Stack

Local development

cd clients/web
bun install   # installs the whole workspace
bun run openapi-ts  # generate API client (required before typecheck/dev)
bun run dev         # Vite dev server on localhost:3000

Connecting to a backend

The Vite dev server includes a built-in reverse proxy that forwards API paths (/v1/*, /_allauth/*, /accounts/*) to the Django backend. This keeps all requests same-origin so session cookies work automatically — no CORS configuration or HTTPS setup needed.

By default the proxy targets http://localhost:8000. To change it, set API_PROXY_TARGET in a .env file (see .env.example):

# .env (not committed)
API_PROXY_TARGET=http://localhost:8000

Browse to http://localhost:3000 and log in normally.

Note: Some API endpoints (avatar, feed, assistant state) return 404 unless the assistant daemon is running. This is expected in frontend-only mode — the core UI and auth flow work without the daemon.

How the proxy works

The client makes relative API requests (e.g. fetch("/v1/feed")). In development, Vite's proxy intercepts these and forwards them to the backend. In production, an infrastructure reverse proxy (nginx, cloud LB, etc.) does the same routing. The client code is identical in both environments — no environment-specific base URLs.

Development:   Browser ─► Vite :3000 ─(proxy)─► Django :8000
Production:    Browser ─► Reverse proxy ─► Django

Reference: Vite server.proxy docs

Other commands

bun run build      # Production build to dist/
bun run preview    # Serve the production build locally
bun run typecheck  # bunx tsc --noEmit
bun run lint       # eslint

Storybook

Storybook provides isolated component development for clients/web components — the ones tied to the app (routing, stores, layout) that don't belong in @vellumai/design-library. Design-system primitives live in their own Storybook (packages/design-library, port 6006).

Both Storybooks are published off main and cross-link to each other from the toolbar above the canvas:

cd clients/web
bun run storybook          # dev server → http://localhost:6007
bun run build-storybook    # static build → storybook-static/

Stories are colocated next to their components (*.stories.tsx). Autodocs generates prop tables from TypeScript types automatically.

Use the Theme toolbar to switch between Light, Dark, and Velvet — the wiring matches the design library Storybook so components render with the same data-theme tokens they get in the running app. A MemoryRouter decorator wraps every story so components using react-router work without setup.

The Storybook also runs automatically as part of vel up (alongside the design library Storybook on :6006) — see the vel README for the multi-service workflow.

Testing

bun test                         # run all tests (single process, fast)
bun test src/path/to/file.test.ts  # run one file
bun run test:ci                  # run each file in its own process (CI)

Tests use Bun's built-in test runner with happy-dom providing browser globals (window, document, localStorage, fetch, etc.) so component and hook tests run without a real browser.

Why test:ci?

Bun's mock.module() mutates a process-global module registry — mocks set in one test file leak into every subsequent file in the same process. bun run test:ci runs each file in its own subprocess for full isolation. Use it when the standard bun test shows cross-file contamination, or in CI where deterministic results are required.

Architecture

See docs/CONVENTIONS.md for code organization (domain-based architecture), component conventions, and framework strategy. See docs/STATE_MANAGEMENT.md for state patterns (Zustand + TanStack Query).

See docs/STYLE_GUIDE.md for naming, imports, TypeScript rules, and formatting.

Feature boundaries are enforced by lint. Each folder under src/domains/ is meant to be a self-contained feature — its own data, components, hooks, and tests. When one feature reaches into another's internals, that creates a hidden coupling: changing the source can break the consumer, even though they're supposed to be independent. The custom ESLint rule local/no-cross-domain-imports fails CI on any new @/domains/<y>/... import from inside src/domains/<x>/... when x !== y. Existing legacy imports are listed in .cross-domain-allowlist.json while we lift the shared pieces up to the top-level shared directories (hooks/, stores/, utils/, types/, components/). That file shrinks toward zero over time — fix violations rather than adding entries to it. See docs/CONVENTIONS.md for the full reasoning and the lift-vs-compose decision tree.

Directory structure

src/
  App.tsx                    # root layout component
  main.tsx                   # entry point (createRoot, RouterProvider)
  routes.tsx                 # route tree (createBrowserRouter)
  assistant/                 # core domain — the assistant itself
  stores/                    # app-level Zustand stores (cross-domain)
  domains/                   # feature modules
    messages/                # message lifecycle
    conversations/           # conversation CRUD, grouping, selection
    streaming/               # SSE transport, event parsing
    interactions/            # user-facing prompts
  hooks/                     # cross-domain shared hooks
  utils/                     # cross-domain shared utilities
  types/                     # cross-domain shared types
  lib/                       # configured third-party wrappers
  runtime/                   # framework adapters, platform bridges
  components/                # cross-domain shared UI
  pages/                     # route-level page components
  generated/                 # auto-generated code (HeyAPI) — gitignored

Path alias

Use @/ to import from src/:

import { useMessageStore } from "@/domains/messages/message-store.js";

Configured in both vite.config.ts (resolve.alias) and tsconfig.json (paths) for editor support.

Why library mode?

React Router v7 ships two modes: library / data-router mode (pure-client SPA built around createBrowserRouter) and framework mode (file-based routes, generated types, per-route code splitting).

Library mode is the established React SPA pattern. Fewer conventions to learn, fewer build-time plugins, no generated types directory — the more recognizable shape for contributors. Framework mode is a defensible alternative when the app grows enough that per-route code splitting becomes worth its conventions; the React Router API used in day-to-day code (<Link>, <Outlet>, useParams, useNavigate) is the same in both modes, so switching later is a restructure rather than a rewrite.

SSR/build-safe rendering

Even though this is an SPA, route and layout components must not access window / localStorage / document during synchronous render. Client-only reads belong in useEffect or in a runtime adapter implementation. This keeps the door open for future static prerendering or hybrid runtimes. See Vite's SSR guidance for the underlying reasoning.